Skip to main content

JSON to XML: Turning Keys Into Tags Without Breaking SOAP

A practical guide to converting JSON to XML — how keys become tags, arrays repeat elements, the single-root rule, attributes vs children, and escaping.

Published By Li Lei
#json #xml #converter #soap #data-formats

JSON to XML: Turning Keys Into Tags Without Breaking SOAP

Most new services speak JSON. Plenty of old ones only speak XML. When those two worlds meet — a REST backend that has to call a SOAP partner, a CMS feed that has to become RSS, a config file that a legacy app insists on reading as XML — you end up needing a reliable way to turn one into the other. The mapping looks simple until you hit the parts JSON does not have: a single root, attributes, and a strict escaping rule. This guide walks through how that conversion actually works, where it bites people, and a full worked example you can copy.

Keys Become Tags, Nested Objects Become Children

The core rule is the one that makes the whole thing intuitive: a JSON object key becomes an XML element name, and the value becomes that element's content. A nested object becomes a nested (child) element, recursing as deep as your data goes.

{ "note": { "to": "Tove", "from": "Jani", "body": "Don't forget me" } }

becomes

<note>
  <to>Tove</to>
  <from>Jani</from>
  <body>Don't forget me</body>
</note>

Every key sat directly inside note, so each one became a child element of <note>. If body had itself been an object — say { "heading": "...", "text": "..." } — you'd get <body> wrapping two grandchildren. There is no depth limit; the serializer just keeps descending. The JSON to XML converter does this recursively and indents each level (2 or 4 spaces, your choice) so the output is readable rather than one long line.

Arrays Repeat the Element, Once Per Item

XML has no array type. The standard idiom is to repeat the same element once for each item, and that is exactly what happens here. The key name supplies the element name; each entry in the array gets its own tag.

{ "list": { "item": ["a", "b", "c"] } }

becomes

<list>
  <item>a</item>
  <item>b</item>
  <item>c</item>
</list>

This is not a quirk — it is how real formats are built. RSS repeats <item>, Atom repeats <entry>, an Android resource file repeats <string>. The same pattern means the output round-trips: feed it back through the XML to JSON converter and the same-name siblings collapse into an array again, so you can move data back and forth without losing shape. One edge case worth knowing: an empty array [] becomes a self-closing <list/>, since there are no items to repeat.

The Single-Root Rule

A well-formed XML document must have exactly one root element. That is a hard rule of the XML spec, not a tool preference, and it trips up almost everyone the first time.

When your JSON has a single top-level key, that key becomes the root for free:

{ "note": { ... } }   →   <note>…</note>

But when your JSON has zero top-level keys, two or more of them, or is an array or a bare primitive at the top level, there is no obvious single root to use. In that case the converter wraps everything in a root element whose name you set (default root). So { "a": 1, "b": 2 } becomes <root><a>1</a><b>2</b></root>. The most common mistake is expecting two top-level keys to stay un-wrapped — they cannot, because the result would be two roots and an invalid document. If you want a specific wrapper name (for SOAP that's often your operation name; for config it might be beans or configuration), set the root element field explicitly.

Attributes vs Children

XML elements can carry attributes — <book id="7"> — and JSON has no concept of them. The convention this tool uses, shared with its XML-to-JSON sibling so the two stay symmetric, is a prefix: any key starting with @ becomes an attribute, everything else becomes a child element.

{ "book": { "@id": "7", "title": "Dune" } }

becomes

<book id="7">
  <title>Dune</title>
</book>

There is a matching #text key for the case where an element needs both attributes and inline text, like { "string": { "@name": "app_title", "#text": "Toolora" } }<string name="app_title">Toolora</string>. Two things to keep in mind. First, attributes can only hold scalar values — a string, number, or boolean — because XML attributes cannot contain nested markup. Point @meta at an object and it is skipped rather than emitted as the nonsense meta="[object Object]". Second, if your JSON marks attributes with _ or some other character, change the prefix field; clear it entirely and every key is treated as an element.

Escaping Is What Keeps the Document Valid

This is the part that separates a converter you can trust from one that hands you broken output. Five characters are special in XML: &, <, >, ", and '. If a value contains them and they are emitted raw, the document is corrupt — or worse, an attacker controlling the input can inject markup.

The converter escapes text content for &, <, and >, and additionally escapes " and ' inside attribute values. So a product note reading Tom & Jerry < cartoons > comes out as:

<note>Tom &amp; Jerry &lt; cartoons &gt;</note>

Element names get sanitized too: spaces and colons become _, and names starting with a digit or with xml get a leading underscore, so a sloppy key like "order id" can't produce malformed markup. (The one caveat: that sanitizing also rewrites a real namespace prefix like soap:Body to soap_Body, so if you need the exact prefix preserved you declare the namespace and use a single-segment local name.)

A Worked Example: A Small SOAP Body

Here is the workflow I reach for most. A while back I had a service that was JSON all the way down internally but had to call one ancient partner over SOAP. Rather than hand-build envelopes, I kept the test fixture as JSON and converted right before the call. Input:

{
  "GetOrder": {
    "@xmlns": "http://partner.example/orders",
    "orderId": "A-1057",
    "note": "Gift wrap — Q&A team",
    "lines": { "line": [
      { "@sku": "BK-9", "qty": "2" },
      { "@sku": "BK-3", "qty": "1" }
    ]}
  }
}

Set the root to GetOrder (already the single top-level key, so it's picked automatically), declaration on, 2-space indent. Output:

<?xml version="1.0" encoding="UTF-8"?>
<GetOrder xmlns="http://partner.example/orders">
  <orderId>A-1057</orderId>
  <note>Gift wrap — Q&amp;A team</note>
  <lines>
    <line sku="BK-9" qty="2"/>
    <line sku="BK-3" qty="1"/>
  </lines>
</GetOrder>

Notice three things working together: the @xmlns and @sku keys became attributes, the line array repeated as two <line> elements (self-closing because they had only attributes and no children), and the & in "Q&A" escaped to &amp; so it never breaks the envelope. I pasted that straight into the <soap:Body> and it parsed first try. The whole point was that I never had to think about the angle brackets — the escaping handled the one detail that always burns people doing this by hand.

When to Use It

Reach for JSON-to-XML conversion whenever the source of truth is more comfortable as JSON but the consumer demands XML: building a SOAP request body from a readable fixture, generating an RSS 2.0 or Atom feed from a CMS content list, authoring Android strings.xml from JSON, or producing a config file for a legacy Spring/.NET/Tomcat app. And because the @attr / #text conventions are symmetric with the XML to JSON converter, you can run a large XML document through JSON, edit it with JSON-native tooling, and rebuild equivalent XML on the way out — keep the prefix and text-node key identical in both directions and the structure survives the trip. Everything runs in your browser tab, so config files and API payloads never leave the machine.


Made by Toolora · Updated 2026-06-13