Skip to content
AgileLogics

XML to JSON

Parses an XML document and converts its element tree into an equivalent JSON structure.

Working on a whole file? Open it in the XML editor →

How it works

Paste XML into the input panel and click Convert.

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
&amp; &lt; &#169; "& < ©" 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

  1. 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.
  2. Click Convert.
  3. If you see "Enter valid XML.", run the same text through the XML validator, which shows the parser's message and position.
  4. 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

Frequently asked questions

How are XML attributes represented in the JSON?
Each attribute becomes a key with an @ prefix on the element's object, so <book id="b1"> turns into {"@id": "b1"}. If the same element also has text, the text goes under a #text key, for example {"@currency": "EUR", "#text": "12.50"}.
Why is one element an array and another a plain object?
Arrays are only created when an element name repeats under the same parent. Two <book> elements give "book": [...], but a single <item> gives "item": "1" with no array. Code that reads the result should handle both shapes, or you can wrap single values in an array yourself.
Are numbers and true/false converted to JSON types?
No. Every text value stays a string, so <year>1965</year> becomes "1965" and <t>true</t> becomes "true". XML has no built-in data types without a schema, and the converter doesn't guess.
Is my XML uploaded anywhere?
No. The conversion uses your browser's built-in DOMParser and JSON.stringify. The XML you paste stays on your device.
What does \"Enter valid XML.\" mean?
The browser's XML parser rejected the input, for example because of a mismatched closing tag, an unescaped & or an HTML entity such as &nbsp; that XML doesn't define. Paste the same text into the XML Validator to see the exact error and where it is.