How to Convert XML to JSON

XML and JSON model data differently — XML has attributes, JSON doesn't; XML allows mixed text-and-element content, JSON doesn't. So every XML-to-JSON conversion picks a convention for how to represent the differences. This guide covers the de-facto standard conventions used by xmltodict, xml-js, and the major XML-to-JSON libraries, with code in JavaScript, Python, PHP, and Java, plus the cases where converting is the wrong move. Test any output against the XML to JSON converter tool — it follows the same conventions described here.

The Conventions

Every XML-to-JSON converter has to answer the same four questions. The conventions below are what xmltodict, xml-js, fast-xml-parser, and the Janeer converter all follow:

  • Attributes: prefixed with @. <book id="1"/>{"book": {"@id": "1"}}
  • Text content alongside attributes: goes into a #text key. <name lang="en">Janeer</name>{"name": {"@lang": "en", "#text": "Janeer"}}
  • Text-only elements: become the string directly. <name>Janeer</name>{"name": "Janeer"}
  • Repeated child tags: collapse into an array. <list><item>A</item><item>B</item></list>{"list": {"item": ["A", "B"]}}

Once you internalize those four rules, every conversion makes intuitive sense. Round-tripping back to XML is also predictable.

Worked Example

Given this XML:

<?xml version="1.0"?>
<library>
  <book id="1" status="available">
    <title>Refactoring</title>
    <author>Martin Fowler</author>
    <tags>
      <tag>design</tag>
      <tag>refactoring</tag>
    </tags>
  </book>
  <book id="2" status="checked-out">
    <title>Domain-Driven Design</title>
    <author>Eric Evans</author>
  </book>
</library>

The expected JSON output:

{
  "library": {
    "book": [
      {
        "@id": "1",
        "@status": "available",
        "title": "Refactoring",
        "author": "Martin Fowler",
        "tags": { "tag": ["design", "refactoring"] }
      },
      {
        "@id": "2",
        "@status": "checked-out",
        "title": "Domain-Driven Design",
        "author": "Eric Evans"
      }
    ]
  }
}

Two books → book becomes an array. Two tags in the first book → tag becomes an array. The second book has no tags element at all, so the key is just absent (not null).

JavaScript

The most popular pure-JS library is fast-xml-parser — no dependencies, fast, configurable:

// npm install fast-xml-parser
import { XMLParser } from 'fast-xml-parser';

const parser = new XMLParser({
  ignoreAttributes: false,
  attributeNamePrefix: '@',
  textNodeName: '#text',
});

const xml = '<book id="1"><title>Refactoring</title></book>';
const obj = parser.parse(xml);
// { book: { '@id': '1', title: 'Refactoring' } }

console.log(JSON.stringify(obj, null, 2));

For a quick conversion without a library, use the browser's built-in DOMParser plus a recursive walker. That's what the Janeer XML to JSON converter does — view source to copy the implementation. xml-js is another popular library with similar conventions and a slightly more verbose default output.

Python

xmltodict is the de-facto standard — install it and you're one line away:

# pip install xmltodict
import xmltodict
import json

xml = '''<book id="1"><title>Refactoring</title></book>'''

# xmltodict uses @ for attributes and #text for mixed content by default
data = xmltodict.parse(xml)
# OrderedDict([('book', OrderedDict([('@id', '1'), ('title', 'Refactoring')]))])

print(json.dumps(data, indent=2))
# {
#   "book": {
#     "@id": "1",
#     "title": "Refactoring"
#   }
# }

# Force certain tags to always be lists (avoids the "one-or-many" ambiguity)
data = xmltodict.parse(xml, force_list=('book',))

For very large XML files, xmltodict.parse accepts a file handle and a streaming callback so you don't load the whole document into memory. For XML with namespaces, pass process_namespaces=True to preserve them, or namespaces={'ns:': 'newprefix'} to remap them.

PHP

The shortest path uses the built-in SimpleXMLElement and json_encode:

<?php
$xml = '<book id="1"><title>Refactoring</title></book>';
$obj = simplexml_load_string($xml);
$json = json_encode($obj, JSON_PRETTY_PRINT);
echo $json;
// {
//   "@attributes": {"id": "1"},
//   "title": "Refactoring"
// }

PHP's json_encode of a SimpleXMLElement puts attributes under @attributes instead of prefixing each one with @ — a slightly different convention than xmltodict and xml-js. For full control, walk the SimpleXMLElement manually:

<?php
function xml_to_array(SimpleXMLElement $xml): array {
  $result = [];
  foreach ($xml->attributes() as $name => $value) {
    $result['@' . $name] = (string) $value;
  }
  foreach ($xml->children() as $name => $child) {
    $value = count($child->children()) || count($child->attributes())
      ? xml_to_array($child)
      : (string) $child;
    if (isset($result[$name])) {
      if (!is_array($result[$name]) || !isset($result[$name][0])) {
        $result[$name] = [$result[$name]];
      }
      $result[$name][] = $value;
    } else {
      $result[$name] = $value;
    }
  }
  return $result;
}

Java

Jackson's XmlMapper reads XML directly into JSON-shaped trees:

// Maven: com.fasterxml.jackson.dataformat:jackson-dataformat-xml
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

XmlMapper xmlMapper = new XmlMapper();
JsonNode tree = xmlMapper.readTree(xmlString);

ObjectMapper jsonMapper = new ObjectMapper();
String json = jsonMapper.writerWithDefaultPrettyPrinter()
                        .writeValueAsString(tree);

For one-off use without Jackson, org.json.XML ships in many Java environments:

import org.json.JSONObject;
import org.json.XML;

String xml = "<book id=\"1\"><title>Refactoring</title></book>";
JSONObject obj = XML.toJSONObject(xml);
System.out.println(obj.toString(2));
// {"book": {"id": "1", "title": "Refactoring"}}

Note that org.json.XML doesn't prefix attributes with @ — it mixes attributes and child elements as flat object keys. That's lossy if an attribute and a child element share a name. Jackson's XmlMapper handles the case correctly.

Common Pitfalls

The "one or many" problem

A repeated child becomes an array; a single child stays a scalar. The same XML schema produces either shape depending on the document. Robust consumers handle both:

// JavaScript
const items = Array.isArray(data.list.item) ? data.list.item : [data.list.item];

# Python
items = data['list']['item']
if not isinstance(items, list): items = [items]

Or configure the library to always emit arrays for known list-typed elements: xmltodict(force_list=('item',)), fast-xml-parser's isArray callback, etc.

Mixed content

XML like <p>Visit <a href="...">our site</a> today.</p> mixes text and elements in a specific order. JSON conventions can preserve the text and the element, but not the order. If your XML is document-style (XHTML, RSS item bodies, formatted prose), don't convert it to JSON — keep it as XML or convert to Markdown/HTML.

Namespaces

XML namespaces look like xmlns:soap="http://..." on the root and soap:Body on child elements. Most converters preserve the prefix verbatim ("soap:Body") but lose the xmlns declaration. When round-tripping back to XML, you may need to add the declaration manually. For SOAP, gRPC over JSON, or other namespace-heavy formats, configure the library explicitly.

Empty elements vs null

Different libraries handle <tag/> and <tag></tag> differently — some produce null, some "", some an empty object. Pick a library and stick with it; convert downstream if the consumer expects a different shape.

Whitespace

Pretty-printed XML has whitespace between elements. Most converters strip it; some preserve it as text nodes (which makes every element look like mixed content). Use a converter that has explicit whitespace handling.

Try It Live

The XML to JSON converter tool follows the conventions described above — paste any XML and see the JSON output (or vice versa) instantly. The conversion runs in your browser, so you can use it with sensitive enterprise XML without sending it anywhere. Pair with the XML formatter to clean up minified XML before conversion, and the JSON formatter to pretty-print the output.

Frequently Asked Questions

What is the standard way to represent XML attributes in JSON?

The widely-used convention is to prefix attribute names with @ in the resulting JSON object: <book id="1">Foo</book> becomes {"book": {"@id": "1", "#text": "Foo"}}. This convention (from BadgerFish and adopted by xmltodict, xml-js, and most modern converters) is unambiguous, round-trips cleanly back to XML, and lets a JSON consumer easily tell attributes apart from child elements. Some older converters used different prefixes ($, _attr, no prefix at all) — match what your downstream consumer expects.

How do I handle repeated child elements when converting XML to JSON?

Collapse them into a JSON array. <list><item>A</item><item>B</item></list> becomes {"list": {"item": ["A", "B"]}}. The convention every major library follows is: one occurrence becomes a single value, two or more become an array. This is convenient but creates a downstream gotcha — the same XML schema can produce either shape depending on how many items the document has. Robust consumers handle both with const items = Array.isArray(x.item) ? x.item : [x.item];. Some libraries (xmltodict with force_list) let you mark certain tags as always-array.

Can I round-trip XML to JSON and back without losing data?

For structured-data XML — elements with attributes, text content, and nested children — yes, the conversion is lossless and round-trips cleanly. The cases where information is lost: comments are stripped, processing instructions are stripped, the order of differently-named sibling elements is preserved but not the order of mixed content (text and elements interleaved), and namespace prefixes survive but the original xmlns declaration may need to be re-added. For document-style XML with significant mixed content (XHTML, formatted prose), JSON is the wrong target format — keep it as XML.

What is the best XML to JSON library for JavaScript, Python, PHP, and Java?

JavaScript: fast-xml-parser (no dependencies, fast, configurable) for production; xml-js is also popular and uses BadgerFish conventions. Python: xmltodict (the de-facto standard, returns Python dicts that JSON-encode trivially). PHP: built-in SimpleXMLElement + json_encode works for simple cases; for control over attributes use simplexml_load_string with manual recursion. Java: Jackson's XmlMapper reads XML directly into JSON-shaped trees; org.json.XML.toJSONObject is a simpler one-liner for ad-hoc use. All of these follow approximately the same conventions for attributes and arrays.

When should I avoid converting XML to JSON?

Skip the conversion when (1) the XML has heavy mixed content — text and elements interleaved freely, like XHTML or formatted prose — because JSON can't preserve the ordering; (2) both your source and target systems handle XML natively, since converting adds a step that can lose information; (3) you're building a system from scratch and could just pick one format and stay with it; (4) the XML uses XSLT transformations or schema validation that JSON tooling can't replicate. Convert when the destination is locked to JSON (modern JavaScript frontends, REST clients), or when you need to query the data with JSON-native tools (jq, MongoDB).