XML to JSON: Mapping Rules and Round Trips

💡 37K+ searches/mo⏱️ 7 min read

XML and JSON disagree about the basics: XML has attributes, JSON doesn't. XML repeats elements freely, JSON doesn't repeat keys. Every XML-to-JSON converter has to make policy decisions about those gaps, and if you don't know the policy, your consuming code breaks in ways that only appear in production. Here are the actual rules — with measured byte counts, not vibes.

Advertisement

The mapping, concretely

The convention most converters follow (xml-js's compact mode, and near enough every API wrapper built on it):

XMLJSONWatch out
<book id="bk101">{"_attributes":{"id":"bk101"}}JSON has no attributes; the prefix marks them
<title>Midnight Rain</title>{"_text":"Midnight Rain"}Text shares the element's object with siblings
Two <book> siblings"book":[{…},{…}]One is an object, many is an array
<?xml version="1.0"?>"_declaration":{…}Usually dropped or ignorable
<ns:tag/>"ns:tag":{}Prefix stays glued; URIs don't resolve

That table is the whole game. Everything else — pretty-printing, key naming, type coercion — is an option, not a mystery. Our XML to JSON converter exposes the two that matter (compact vs verbose structure, indentation) and nothing else, because those are the two your downstream actually notices.

The measured cost of converting

Numbers from a real run on a two-book catalog (each book: an id attribute, author, title, and a price element with a unit attribute):

The size inflation is structural. JSON spells out _attributes and _text for every element, and repeats the element name as a key. Element-heavy XML pays that tax per element; attribute-heavy XML (config files, plist-style documents) can actually shrink, because id="bk101" is cheaper than <id>bk101</id> wrapped in an object.

The singular-vs-array trap

This is the bug that pages you at 2 a.m. An XML feed with one <order> gives you data.order.id. The same feed with two orders gives you data.order[0].id — your consumer throws on the first shape or the second, depending on which day you wrote it. And zero orders can make the key vanish entirely.

The fix belongs in consuming code, not the converter: normalize. In JavaScript, const orders = [].concat(data.order || []) handles all three shapes in one line. Any converter that "solves" this by forcing every element into an array solves it by making every document fatter and every path longer — the cure is worse than the disease, which is why good converters leave the choice to you.

When is a round trip safe?

A conversion round trip (XML → JSON → XML) is byte-exact when three things hold:

  1. Spacing is normalized — whitespace-only text between elements is ignorable, and you regenerate at the same indent level.
  2. Nothing exotic is in the document — comments, CDATA, and processing instructions survive only via reserved keys; DTDs and entities beyond the five built-ins generally don't round-trip.
  3. Attribute and element order is preserved — which the object model does keep, since JSON object key order is preserved by every serializer that matters in practice.

Under those conditions, convert your fixtures to JSON, keep them in version control as JSON, and regenerate the XML whenever a legacy consumer asks. The byte-exact property means your diffs stay clean — no phantom changes from a converter that reorders attributes.

Namespaces: the quiet lossy part

<ns:tag> becomes the literal key "ns:tag", and the xmlns:ns="…" declaration converts as an ordinary attribute. Round-trip that and you get valid XML back with the same prefixes. What you lose is resolution: if two different parents both use prefix a for different namespaces, the JSON can no longer tell them apart. If namespaces carry meaning in your pipeline (SOAP, SAML, XHTML), resolve or rewrite them before converting — or use the XML formatter to inspect the structure first and convert deliberately.

Should you convert at all?

Sometimes the honest answer is no. If the XML is the system of record — a SOAP contract, a SAML assertion, an industry schema — parse it as XML and keep it as XML; bolting JSON in the middle adds a mapping layer that drifts. Convert when JSON is the destination's native tongue: browsers, JavaScript services, document stores like MongoDB or DynamoDB, and most modern REST layers. And when the source is really tabular, skip both formats' idiosyncrasies with the CSV to JSON converter — tables convert cleaner as tables.

Convert one right now

Paste XML, get JSON — flip the toggle to go back. Byte-exact round trips, entirely in your browser.

XML to JSON Converter →

The bottom line

XML to JSON is a solved problem with sharp edges. The mapping is mechanical — attributes to _attributes, text to _text, repetition to arrays — and the failure modes are predictable: shape-shifting arrays, namespace ambiguity, and threefold size growth on element-heavy documents. Know the policy your converter uses, normalize repeated fields in consuming code, and verify round trips before trusting them with fixture data. The two-way converter shows its counts (elements, attributes, text nodes) on every conversion precisely so you can check, not trust.

Advertisement

Frequently Asked Questions

How do XML attributes become JSON keys?

A converter has to invent a place for attributes, because JSON has no attribute concept. The common convention (used by xml-js) nests them under an _attributes object: <book id="bk101"> becomes {"book":{"_attributes":{"id":"bk101"}}}. Text content gets a sibling _text key. The underscore prefix stops attributes from colliding with child elements that share a name.

Why did my JSON field change from an object to an array?

Because XML repetition maps to JSON arrays. One <book> element produces an object; two or more produce an array of objects. If the feed you consume has zero-or-many entries, the field can disappear entirely (zero), be an object (one), or be an array (many). Defensive consuming code normalizes every such field to an array before reading it.

Is XML to JSON lossless?

Almost. Element order, attribute order, and escaping survive, and a compact round trip can be byte-exact. What degrades: comments and processing instructions survive only if the converter keeps reserved keys for them, mixed content (text interleaved with elements) forces the text into positioned fragments, and namespace prefixes usually stay glued to element names instead of resolving to their URIs.

Why is my JSON three times bigger than the XML?

JSON repeats the key name for every element and wraps attributes and text in extra objects. Measured on a two-book catalog: 254 bytes of XML becomes 710 bytes of pretty-printed JSON — 2.8x. Minifying the JSON and using attribute-heavy XML (instead of element-heavy) both shrink the gap, and attribute-heavy documents can actually come out smaller.

Do XML namespaces survive conversion to JSON?

As prefixes, yes; as resolved namespaces, rarely. <ns:tag> typically becomes the literal key "ns:tag", and the xmlns declaration becomes an attribute like any other. If two prefixes from different parents mean different namespaces, the JSON no longer distinguishes them — resolve namespaces before converting if your consumer cares.

Related Tools