← All articles

XML to JSON Conversion: Attributes, Text Nodes, Arrays, and Namespaces

Convert XML to JSON the right way: how attributes, text nodes, repeated elements, and namespaces map to JSON — with conventions, edge cases, and JS/Python code.

Converting XML to JSON sounds mechanical until you hit the parts of XML that JSON has no direct equivalent for: attributes, mixed text-and-element content, elements that appear once or many times, and namespaces. There's no single "correct" mapping — only conventions. This guide explains the standard conventions, the decisions you have to make, and how to convert XML to JSON in JavaScript, Python, and the browser.

The Mapping at a Glance

Most XML-to-JSON converters (xmltodict, fast-xml-parser, and this site's tool) follow the same shape: each element becomes an object, attributes become specially-prefixed keys, and text becomes either the value or a reserved key.

<!-- XML -->
<note id="1" priority="high">
  <to>Ada</to>
  <from>Bob</from>
  <body>Hello &amp; welcome</body>
</note>
// JSON
{
  "note": {
    "@id": "1",
    "@priority": "high",
    "to": "Ada",
    "from": "Bob",
    "body": "Hello & welcome"
  }
}

Attributes → @-Prefixed Keys

JSON objects have no notion of attributes, so the near-universal convention is to prefix attribute names with @. That keeps them distinct from child elements and makes the mapping reversible.

<book id="b1" lang="en"/>
→ { "book": { "@id": "b1", "@lang": "en" } }

Some tools use a different prefix ($, _) or a nested "@attributes" object. Pick one and apply it consistently — downstream code needs to know where attributes live.

Text Nodes and Mixed Content

When an element contains only text, it collapses to a string value. But when an element has attributes and text, the text needs somewhere to go — conventionally the key #text.

<price currency="USD">9.99</price>
→ { "price": { "@currency": "USD", "#text": "9.99" } }

<title>Effective TypeScript</title>
→ { "title": "Effective TypeScript" }

Truly mixed content (text interleaved with child elements, as in HTML-like markup) is the hardest case — most data-oriented converters concatenate or drop the loose text. If your XML is document-style rather than data-style, expect lossiness here.

The Single-vs-Array Problem

This is the trap that breaks more code than any other. An element that appears once becomes an object; the same element appearing twice becomes an array. So the JSON shape depends on the data, not the schema:

<tags><tag>a</tag></tags>
→ { "tags": { "tag": "a" } }          // object

<tags><tag>a</tag><tag>b</tag></tags>
→ { "tags": { "tag": ["a", "b"] } }   // array

Consumers that expect tags.tag to always be an array will crash on the single-item case. Two fixes: configure the parser to always treat known repeatable elements as arrays, or normalise after parsing (const arr = [].concat(node.tag ?? [])).

Namespaces

XML namespaces use prefixes (soap:Envelope) bound by xmlns declarations. JSON has no namespace concept, so converters typically take one of these approaches:

  • Keep the prefix in the key"soap:Envelope". Simple and reversible, but the key contains a colon, so you'll need bracket access (obj["soap:Envelope"]).
  • Strip the prefix"Envelope". Cleaner keys, but you lose the namespace and risk collisions between two namespaces that use the same local name.
  • Keep xmlns as attributes — the declarations become "@xmlns:soap" keys so the binding survives the round-trip.

For most data tasks, keeping the prefix in the key is the safest default — it never loses information.

Entities and CDATA

A correct converter decodes the five predefined entities (&lt;, &gt;, &amp;, &quot;, &apos;) and numeric references (&#169;) into their characters, and treats <![CDATA[...]]> blocks as literal text.

Convention Names: BadgerFish, GData, Parker

The "@ for attributes / #text for text" mapping isn't the only scheme in the wild. Three named conventions you'll hit when reading other systems' XML→JSON output:

  • BadgerFish — attributes go under a key prefixed with @; text under $; namespace declarations under @xmlns. Verbose but lossless.
  • GData — Google's variant: attributes prefixed with $; text under $t; repeated elements always become arrays. Lossless, predictable shape.
  • Parker — strips attributes entirely; the simplest, lossiest mapping. Useful when you control both sides and only care about element values.

When you're integrating with a system that already produces JSON-from-XML, identify which convention it uses before you write parsing code.

Querying the Result with JSONPath

Once the XML is converted, you can address values with JSONPath. Two small adjustments versus XPath habits:

  • Attribute keys carry the @ prefix from the mapping, so XPath's @id becomes JSONPath's $..['@id'].
  • Single vs array (above) means a working XPath like book/title may need to be $..book[*].title in JSONPath to handle both shapes.

How to Convert XML to JSON in Code

// JavaScript (browser) — DOMParser + a small walker, or a library:
import { XMLParser } from 'fast-xml-parser';
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@' });
const obj = parser.parse(xmlString);

# Python — xmltodict maps attributes to "@name" and text to "#text"
import xmltodict, json
doc = xmltodict.parse(xml_string)
print(json.dumps(doc, indent=2))

Convert XML to JSON Online

For a quick conversion, paste your XML into the JSON ⇄ XML converter and click To JSON. It applies the conventions above — @ for attributes, #text for mixed content, arrays for repeated elements — and runs entirely in your browser, so internal feeds and API payloads never leave your machine.

Frequently Asked Questions

How are XML attributes represented in JSON?

By convention they become keys prefixed with @ (e.g. @id), keeping them distinct from child elements so the mapping can be reversed.

Why does the same element sometimes become an object and sometimes an array?

Because the shape follows the data: one occurrence maps to an object, multiple occurrences map to an array. Configure your parser to always treat known repeatable elements as arrays, or normalise with [].concat(value) after parsing.

What happens to text inside an element with attributes?

It's stored under the reserved key #text, since the object already holds the attributes. An element with only text collapses to a plain string.

How are XML namespaces handled?

JSON has no namespaces. The safest approach keeps the prefix in the key ("soap:Envelope") and the xmlns declarations as @xmlns:* attributes, so nothing is lost.

Related Tools & Guides