JSON to XML Converter

Turn JSON into well-formed XML with attributes, repeated elements, safe tag names and proper escaping.

Overview

Plenty of systems still only accept XML: SOAP endpoints, payment and EDI gateways, government filing portals, older enterprise service buses and many import screens in ERP software. If your data starts life as JSON, you need a conversion that produces XML those systems will actually parse, which means legal element names, correctly escaped text and exactly one root element.

This converter maps JSON to XML with a fixed, documented convention: keys that start with @ become attributes, #text becomes element text, and arrays become repeated elements. Keys that are not valid XML names are repaired and listed in the status bar so you can see exactly what changed. The conversion runs entirely in your browser and nothing is uploaded.

Why JSON to XML needs a convention

JSON has objects, arrays, strings, numbers, booleans and null. XML has elements, attributes, text, comments and processing instructions, and it has no types at all. Nothing in plain JSON says whether "id": 7 should be <id>7</id> or id="7", and JSON has no notion of an array as a container: XML expresses a list by repeating an element. So any converter needs rules, and the target system usually decides which rules are right.

The rules here are the inverse of the XML to JSON converter, so data can make the round trip. The usual weak spot is a list with one entry: XML that contains a single <item> looks exactly like XML for a non-list field, and whoever reads it back has to know from a schema that it is a list.

The conversion rules

JSONXML
"name": "Ada"<name>Ada</name>
"@id": "A-1"id="A-1" on the parent element
"#text": "Keyboard"text content of the parent element
"tag": ["a", "b"]<tag>a</tag><tag>b</tag>
"note": null<note/>, or <note xsi:nil="true"/>
"note": ""<note></note>
"#comment": "text"<!--text-->

A complete example:

{
  "order": {
    "@id": "A-1",
    "item": [
      { "@sku": "KB-01", "#text": "Keyboard" },
      { "@sku": "MS-07", "#text": "Mouse" }
    ],
    "gift": false
  }
}

<?xml version="1.0" encoding="UTF-8"?>
<order id="A-1">
  <item sku="KB-01">Keyboard</item>
  <item sku="MS-07">Mouse</item>
  <gift>false</gift>
</order>

The root element. A JSON object with one key whose value is not an array uses that key as the root, as above. Anything else, meaning an object with several keys, a single key holding an array, a bare array or a scalar, is wrapped in the element named in Root (default root), because an XML document must have exactly one. Items of a top-level array, and of arrays nested directly inside arrays, are named with Array item (default item). An empty array produces no elements, so that key disappears from the output.

Nulls. By default null becomes an empty element. If the receiving schema distinguishes "empty" from "missing", tick null as xsi:nil; the converter then writes xsi:nil="true" and declares the xsi namespace on the root element.

Invalid names and escaping

JSON keys can be any string, but an XML name must start with a letter or underscore and may only contain letters, digits, hyphens, periods and underscores (plus a colon for a namespace prefix). The converter replaces every invalid character with _ and adds a leading _ when the first character is not allowed:

"shipping address"  →  <shipping_address>
"2fa"               →  <_2fa>
"$meta"             →  <_meta>
"price (EUR)"       →  <price__EUR_>

Every rename is listed in the status bar. If two keys end up with the same name, both are written as repeated elements, so check the list when your keys differ only by punctuation. Unicode letters are valid in XML names, so "größe" stays as it is.

Text values are escaped so the output always parses: &, < and > become &amp;, &lt; and &gt;. Attribute values additionally escape " and ' as &quot; and &apos;, and line breaks and tabs as character references, because a parser would otherwise normalise them to spaces. With Text as CDATA, string values are wrapped in <![CDATA[...]]> instead, and any ]]> inside the text is split across two sections. Control characters that XML 1.0 forbids, such as U+0000, are removed and counted in the status bar.

When you need it, and what to watch for

  • Calling a SOAP or XML-only API from code that builds JSON. Use keys like "soap:Envelope" and "@xmlns:soap" to produce namespaced elements; a single colon between two valid names is kept.
  • Generating config files such as Maven pom.xml fragments or Android resources from a script.
  • Building RSS feeds from a JSON list of posts, using "item": [...] inside channel.
  • Number precision. JSON.parse stores numbers as 64-bit floats, so an ID such as 12345678901234567890 is already changed before conversion. The converter warns when it sees one; put such values in quotes.
  • Types are lost. true, "true" and 1 all become text in XML. If the receiver needs types, it gets them from its XML Schema, not from the document.

If the JSON does not parse, the error shows the line and column; the JSON validator gives more detail. To tidy XML you already have, use the XML formatter.

Frequently Asked Questions

How do I create XML attributes from JSON?

Prefix the key with @, for example "@id": "A-1". You can switch the prefix to an underscore or a dollar sign to match JSON produced by another tool.

Why was my JSON wrapped in a root element?

An XML document must have exactly one root element. When the JSON has several top-level keys, is an array or is a single value, the converter wraps it in the element name you set under Root.

Why were some of my keys renamed?

XML element names cannot contain spaces or most punctuation and cannot start with a digit. Invalid characters are replaced with underscores, and the status bar lists every key that changed.

Should I use CDATA?

Usually escaping is enough and every XML parser handles it. CDATA is useful when a person will read the XML and the text contains a lot of markup, such as embedded HTML.

Is my data sent to a server?

No. The JSON is parsed and converted in your browser. Your last input is stored only in your browser's local storage.