<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gianluigi</title>
    <description>The latest articles on DEV Community by Gianluigi (@a35g).</description>
    <link>https://dev.to/a35g</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4125171%2F6ed81fe5-7a08-455b-a8f7-17755e988d52.jpg</url>
      <title>DEV Community: Gianluigi</title>
      <link>https://dev.to/a35g</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/a35g"/>
    <language>en</language>
    <item>
      <title>How to convert JSON to XML in PHP</title>
      <dc:creator>Gianluigi</dc:creator>
      <pubDate>Mon, 14 Sep 2026 22:10:21 +0000</pubDate>
      <link>https://dev.to/a35g/how-to-convert-json-to-xml-in-php-547j</link>
      <guid>https://dev.to/a35g/how-to-convert-json-to-xml-in-php-547j</guid>
      <description>&lt;p&gt;If you've ended up here, you probably already know &lt;em&gt;why&lt;/em&gt; you need this: some API, some legacy SOAP endpoint, or some client integration only speaks XML, while everything in your PHP app — your database layer, your other APIs, your frontend — speaks JSON. So now you need a bridge.&lt;/p&gt;

&lt;p&gt;This guide walks through the straightforward way to do it, the problems that show up the moment your JSON isn't trivial, and a small library that handles those problems for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive approach
&lt;/h2&gt;

&lt;p&gt;For a flat JSON object, converting to XML looks almost too easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jsonString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleXMLElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;root/&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;arrayToXml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;SimpleXMLElement&lt;/span&gt; &lt;span class="nv"&gt;$xml&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$xml&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;arrayToXml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$child&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$xml&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;htmlspecialchars&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;arrayToXml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$xml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$xml&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;asXML&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For something like &lt;code&gt;{"nome": "Mario", "eta": 34}&lt;/code&gt; this works fine. The trouble starts as soon as real-world JSON shows up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the naive approach breaks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repeated elements.&lt;/strong&gt; JSON has no native way to say "repeat this tag." A list like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Nota"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"Principale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"Principale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;needs to become two sibling &lt;code&gt;&amp;lt;Nota&amp;gt;&lt;/code&gt; elements, not one &lt;code&gt;&amp;lt;Nota&amp;gt;&lt;/code&gt; containing an array-looking mess, and not a &lt;code&gt;&amp;lt;Nota&amp;gt;&amp;lt;item&amp;gt;...&amp;lt;/item&amp;gt;&amp;lt;item&amp;gt;...&amp;lt;/item&amp;gt;&amp;lt;/Nota&amp;gt;&lt;/code&gt; wrapper that nobody asked for. The recursive function above doesn't know the difference between an indexed list and an associative array — you have to write that check yourself, and it's easy to get subtly wrong (empty arrays, lists of scalars, lists of objects all need slightly different handling).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attributes.&lt;/strong&gt; XML has a whole content model — &lt;code&gt;&amp;lt;request type="ABC"&amp;gt;&lt;/code&gt; — that JSON simply doesn't have a convention for. You need to invent one (usually a prefixed key like &lt;code&gt;@type&lt;/code&gt;) and then handle it as a special case in your recursion, separately from child elements, and validate that nobody tries to put an array where an attribute value should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Special characters.&lt;/strong&gt; &lt;code&gt;htmlspecialchars()&lt;/code&gt; on every value works, but it also means a field like &lt;code&gt;"nota": "Il valore è &amp;lt; 10"&lt;/code&gt; comes out escaped as &lt;code&gt;&amp;amp;lt; 10&lt;/code&gt; — technically valid XML, but often not what a downstream consumer expects when the content itself looks like markup. Sometimes you actually want &lt;code&gt;&amp;lt;![CDATA[...]]&amp;gt;&lt;/code&gt; instead, and deciding &lt;em&gt;when&lt;/em&gt; automatically (only when the content actually needs it) is more logic than it looks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mixed content.&lt;/strong&gt; What about a node that needs both an attribute &lt;em&gt;and&lt;/em&gt; direct text, like &lt;code&gt;&amp;lt;operatore codArea="XXX"&amp;gt;testo&amp;lt;/operatore&amp;gt;&lt;/code&gt;? That's a case the "everything is either a scalar or a nested array" model doesn't represent at all.&lt;/p&gt;

&lt;p&gt;None of these are exotic — they show up in the first real payload you throw at this. At that point you're maintaining a small, undertested XML serializer instead of shipping a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  A library that handles the edge cases
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/A35G/json-to-xml" rel="noopener noreferrer"&gt;&lt;code&gt;a35g/json-to-xml&lt;/code&gt;&lt;/a&gt; is a small, dependency-free PHP library built specifically around these conventions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require a35g/json-to-xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;A35G\JsonToXml\JsonToXmlConverter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$converter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JsonToXmlConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'request'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$converter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;jsonToXmlString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{
    "@type": "ABC",
    "cliente": "Mario Rossi",
    "note": [
        {"#text": "Prima nota"},
        {"#text": "Seconda nota"}
    ]
}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;request&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"ABC"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;cliente&amp;gt;&lt;/span&gt;Mario Rossi&lt;span class="nt"&gt;&amp;lt;/cliente&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;note&amp;gt;&lt;/span&gt;Prima nota&lt;span class="nt"&gt;&amp;lt;/note&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;note&amp;gt;&lt;/span&gt;Seconda nota&lt;span class="nt"&gt;&amp;lt;/note&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/request&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One call. No manual recursion, no wrapper element around the repeated &lt;code&gt;note&lt;/code&gt; nodes, no &lt;code&gt;htmlspecialchars&lt;/code&gt; calls scattered around. The conventions are consistent and documented:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JSON&lt;/th&gt;
&lt;th&gt;XML&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"name": "value"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;child element&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"@name": "value"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;attribute on the current node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"#text": "value"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;direct text content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"name": [ {...}, {...} ]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; repeated once per element, no wrapper&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Special characters are handled automatically too — CDATA is applied only when the content actually needs it (a value containing &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, quotes, or a newline), and can be turned off entirely if you'd rather always use standard XML escaping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$converter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JsonToXmlConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'root'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;useCdata&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather write straight to a file, or stream the XML directly as an HTTP response, both are one-liners:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$converter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;jsonToXmlFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jsonString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'output.xml'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// or, e.g. inside a controller action:&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type: application/xml'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$converter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;jsonToXmlStdOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jsonString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What about going back the other way?
&lt;/h2&gt;

&lt;p&gt;If you also need XML → JSON — say, you're consuming a third-party XML API and want to work with it as a normal PHP array — the same package ships &lt;code&gt;XmlToJsonConverter&lt;/code&gt;, using the identical &lt;code&gt;@attribute&lt;/code&gt; / &lt;code&gt;#text&lt;/code&gt; / repeated-element conventions in reverse, plus built-in protection against XXE attacks when parsing XML from untrusted sources. That's a big enough topic on its own to get its own article — including the one case that genuinely can't be made symmetric: telling the converter that a tag should &lt;em&gt;always&lt;/em&gt; come back as a list, even when there's only one of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Converting a flat JSON object to XML is a five-line function. Converting &lt;em&gt;real&lt;/em&gt; JSON — with lists, attributes, and mixed content — into XML that a strict downstream consumer will actually accept is a small library's worth of edge cases. If you're about to write that recursive function yourself, it's worth the thirty seconds of &lt;code&gt;composer require&lt;/code&gt; first.&lt;/p&gt;

</description>
      <category>php</category>
      <category>composer</category>
      <category>json</category>
      <category>xml</category>
    </item>
  </channel>
</rss>
