How does XML map to JSON?
XML and JSON don't have the same shape. XML has attributes, repeated sibling elements, text nodes and namespaces, while JSON only has objects, arrays, strings, numbers, booleans and null. An XML to JSON converter has to pick a rule for each of those differences. This one uses a widely copied convention: attributes become @ keys, repeats become arrays, and text is a string.
The input is parsed by your browser's native XML parser (DOMParser), so it has to be well-formed according to XML 1.0. The output is JSON as defined by RFC 8259, indented with two spaces. The root element's name becomes the single top-level key.
Mapping rules, with what actually comes out
Every row below was produced by running the converter:
| XML input | JSON output | Rule |
|---|---|---|
<greeting>hi</greeting> |
{"greeting": "hi"} |
Text-only element becomes a string |
<price currency="EUR">12.50</price> |
{"price": {"@currency": "EUR", "#text": "12.50"}} |
Attributes get @, text moves to #text |
<c attr="1"/> |
{"c": {"@attr": "1"}} |
No #text key when there's no text |
<a/> or <b></b> |
"a": "" |
Empty element becomes an empty string |
<items><item>1</item></items> |
{"items": {"item": "1"}} |
One child stays a single value |
two <book> siblings |
"book": [ {...}, {...} ] |
Repeated names become an array |
<code><![CDATA[a < b]]></code> |
"code": "a < b" |
CDATA becomes plain text |
<n> 42 </n> |
"n": "42" |
Text is trimmed, and stays a string |
<media:thumbnail url="x.jpg"/> |
"media:thumbnail": {"@url": "x.jpg"} |
Prefixes are kept in key names |
xmlns:media="..." on an element |
"@xmlns:media": "..." |
Namespace declarations look like attributes |
& < © |
"& < ©" |
Entities are decoded |
<p>Hello <b>world</b> again</p> |
{"p": {"#text": ["Hello", "again"], "b": "world"}} |
Text beside child elements goes under #text |
Comments, processing instructions and the <?xml ...?> declaration are dropped.
Worked example
This XML has attributes, a repeated element and a single element side by side:
<library>
<book id="b1" lang="en">
<title>Dune</title>
<year>1965</year>
</book>
<book id="b2">
<title>Neuromancer</title>
<year>1984</year>
</book>
<note>Two books</note>
</library>
Converting it gives:
{
"library": {
"book": [
{
"@id": "b1",
"@lang": "en",
"title": "Dune",
"year": "1965"
},
{
"@id": "b2",
"title": "Neuromancer",
"year": "1984"
}
],
"note": "Two books"
}
}
Notice that the second book simply has no @lang key, and that year is "1965", a string. If you need it as a number, convert it in your own code after parsing.
Steps
- Paste the XML, including the declaration if you have one. A leading
<?xml ...?>is fine, but it must be the very first thing in the input. - Click Convert.
- If you see "Enter valid XML.", run the same text through the XML validator, which shows the parser's message and position.
- Copy the JSON. To pull specific values out of it, the JSONPath tester works well on the result.
Where structure gets simplified
No text or attribute value is dropped, but three things about the document's shape don't survive the trip into JSON, and it's worth checking your document for them before relying on the output:
- Position of mixed-content text. When an element contains both text and child elements, the text is kept under
#text: a single string for one run of text, or an array when child elements split it into several runs.<p>Hello <b>world</b> again</p>converts to{"p": {"#text": ["Hello", "again"], "b": "world"}}. The words are all there, but not where "world" sat between them, and each run is trimmed. For XHTML, DocBook or other prose-heavy markup, a format that keeps node order is a better fit. - Order of interleaved siblings.
<log><info>start</info><warn>disk</warn><info>end</info></log>becomes{"info": ["start", "end"], "warn": "disk"}. The fact that the warning came between the two info lines is lost, because JSON object keys group by name. - Namespace URIs on elements. Element keys use the prefix as written (
media:thumbnail), not the namespace URI. Two documents that bind different prefixes to the same namespace produce different keys. The@xmlns...keys keep the declarations, so you can resolve them yourself if it matters.
The array rule causes the most bugs in practice. A feed with one <item> produces an object, and the same feed with two produces an array, so a consumer written against one shape breaks on the other. Normalise this in the code that reads the JSON.
Going the other way
The JSON to XML converter does the reverse and understands the same conventions, turning @ keys back into attributes and #text back into text. A round trip gives back the same elements, attributes and values; what it can't restore is the original sibling order and the position of mixed-content text described above. For simply reading a large XML file, the XML formatter is a better fit, and if your goal is tidy JSON afterwards, the JSON formatter can re-indent or check what you've edited.
Last updated