XML to JSON
Read an XML payload as the object shape your code will actually see
XML Input
JSON Output
Reading an XML payload as JSON
A carrier billing gateway answers in SOAP. A vendor's nightly feed is a 4 MB .xml. An MME config predates everyone on the team. You do not want to rewrite any of it — you want to see the shape, so you can write response.subscriber[0].msisdn and be right the first time. Paste the document on the left and the JSON your code would traverse appears on the right.
There are only four rules to the mapping, and knowing them saves the guessing. An element becomes a key. A tag repeated under the same parent becomes an array; a tag that appears once does not. Attributes are collected into an @attributes object so <subscriber id="1"> cannot collide with a child called id. Text sitting beside child elements lands under #text. That is the whole contract.
The second rule is the one that bites, and it bites in production rather than here. Feed this a roster with two <subscriber> elements and you get "subscriber": [ … ]. Feed it the same roster on a quiet day, when only one subscriber came back, and you get "subscriber": { … } — an object, not a one-element array. Code written against the first response does .map() on the second and throws. XML has no way to say "this is a list that happens to have one item in it", so every XML-to-JSON mapper has this problem; a schema would resolve it and there is rarely a schema. Normalise with [].concat(x) on the receiving side and stop thinking about it.
Parsing is the browser's own DOMParser — the same one your page already uses, following the W3C XML 1.0 recommendation — and the output is RFC 8259 JSON at two-space indentation. It all happens in this tab, which matters more than usual here: XML configs are where connection strings and gateway credentials tend to live.
Converting a document
- Paste the XML – One root element, well-formed. If it came out of a browser view-source or a log file, check for a stray
&— a bare ampersand is not valid XML and is the most common reason a document that "looks fine" is rejected. - Read the JSON as you type – No Convert button. A short debounce re-parses after you stop typing, so you can delete a section and watch the shape change instead of converting, reading, and going back.
- Look at what happened to the root – The root element's name is not in the output.
<subscribers>holding two<subscriber>children converts to{ "subscriber": [ … ] }, not{ "subscribers": { "subscriber": [ … ] } }. You get the root's contents, one level shallower than the file. - Check every value you meant as a number – They are all strings.
<rsrp>-92</rsrp>becomes"-92", and<roaming>true</roaming>becomes"true"— which is truthy, so anif (s.roaming)written against it is true for"false"as well. - Copy or download – Copy puts it on the clipboard; Download writes a
.jsonfile. From there JSON to Table will show it as a grid if you would rather scan rows than braces.
Namespaces survive, prefix and all. A SOAP envelope converts to keys like soap:Body, and the xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" declaration itself shows up in the root's @attributes — it is an attribute, so it is treated as one. Nothing here resolves a namespace URI or strips a prefix, because guessing would make the output unpredictable. If a colon in a key upsets a downstream parser, rename after conversion. And no, the URI is not a web page; opening it is a rite of passage every junior goes through once.
Two subscribers, and the array rule in action
The point of this one is the second <subscriber>. With it, subscriber is a JSON array of two objects. Delete it and the same document converts to a bare object — that is the promotion rule, seen once so you recognise it later. Note also that <subscribers>, the root, is nowhere in the output, and that the 19-digit iccid comes through as a string with every digit: XML has no number type, so nothing here ever parses it and nothing can round it.
<?xml version="1.0" encoding="UTF-8"?> <subscribers> <subscriber id="1"> <msisdn>447700900142</msisdn> <iccid>8901240544102066246</iccid> <roaming>true</roaming> </subscriber> <subscriber id="2"> <msisdn>447700900458</msisdn> <iccid>8901240544102066253</iccid> <roaming>false</roaming> </subscriber> </subscribers>
{
"subscriber": [
{
"@attributes": {
"id": "1"
},
"msisdn": "447700900142",
"iccid": "8901240544102066246",
"roaming": "true"
},
{
"@attributes": {
"id": "2"
},
"msisdn": "447700900458",
"iccid": "8901240544102066253",
"roaming": "false"
}
]
}When this is the tool you want
Getting at the body of a SOAP response
Carrier billing gateways, banking middleware and government data exchanges still answer in XML wrapped in an envelope. Paste the whole response and you get { "soap:Body": { … } } — one key down and you are looking at the payload, with the field names your code will use. The W3C SOAP 1.2 recommendation is the reference for the envelope itself, if the fault structure is what you are chasing.
Diffing two XML configs without the noise
HSS, MME and SGSN elements load settings from XML, and so does every Java application written before about 2012. A plain text diff of two of them reports attribute order and whitespace as changes. Convert both here, then run them through JSON Diff, and what you see is the difference that actually changes behaviour.
Looping over an RSS, Atom or sitemap feed
All three are XML, and all three have exactly the repeated-element shape this page is good at: <item> or <entry> repeated becomes an array you can iterate. Worth remembering that a feed with a single entry converts to an object, so guard for it before you ship the scraper.
Building a test fixture from a real response
Capture one production response, convert it, and you have a fixture with the exact field names and the exact nesting — including the @attributes keys that are easy to forget when writing one by hand. JSON Formatter tidies it afterwards if the indentation drifted.
What the conversion actually does
- Repeated tags become arrays, once there are two of them. The first
<subscriber>creates an object; the second promotes it to an array and pushes both in. A lone one stays an object — see the FAQ, because this is the behaviour most likely to break something downstream. - Attributes go under
@attributes.<subscriber id="1">converts to"@attributes": { "id": "1" }, which is what keeps an attribute calledidfrom overwriting a child element calledid. - CDATA survives.
<note><![CDATA[<b>see</b> & co]]></note>gives you"note": "<b>see</b> & co", markup and ampersand intact. A converter that only collects text nodes misses CDATA entirely and returnsnullfor that element — which is exactly the shape RSS feeds and SOAP faults use for their payloads. - Mixed content is not thrown away.
<a>note<b>1</b></a>converts to{ "b": "1", "#text": "note" }. Whitespace between indented elements is ignored, so a pretty-printed file does not sprout a#texton every node. - Every leaf is a string, and that is the honest answer. XML carries no type information, so
-92comes back as"-92". It also means a 19-digit ICCID is never parsed and therefore never rounded — a long identifier reaches the JSON with all its digits. - Runs in this tab. An XML config with a connection string in it is not uploaded anywhere.
Questions people actually ask
Why is my array sometimes an object?
Because the promotion depends on how many elements the document happened to contain. Two <subscriber> tags give you "subscriber": [ … ]; one gives you "subscriber": { … }. XML has no syntax for "a list of one", so no converter can tell the difference without a schema, and there is usually no schema. Wrap the value in [].concat(value) where you read it and the whole class of bug disappears. This is worth doing even when the sample you tested against had ten of them.
Where did my root element go?
The output is the root's contents, not the root itself. <subscribers>…</subscribers> converts to { "subscriber": [ … ] } — the file has one more level of nesting than the JSON. If you need the wrapper back, add it yourself: the name is right there at the top of your source, and re-inventing it here would mean guessing at cases where the root name and a child name are the same.
Why is an empty element an empty object rather than null?
<a/> and <a></a> both have no child nodes at all, so there is nothing to collect and the result is an empty object. An element that contains only whitespace is different — the whitespace is discarded and you get null. Neither is a wrong answer; XML genuinely does not distinguish "empty" from "absent" the way JSON does, and any converter has to pick.
Are numbers and booleans typed?
No, and be careful with the boolean case in particular. <roaming>false</roaming> becomes the string "false", which is truthy in JavaScript — if (subscriber.roaming) is true for both values, and the bug is silent. Cast explicitly where you read it, where you know the intended type. On the upside, this is why a 19-digit <iccid> is safe here: nothing ever converts it to a number, so nothing rounds it.
It says Invalid XML but the document looks right.
Three causes, in the order they turn up. A bare & in a value — it has to be &, and a URL with a query string is where this usually hides. More than one root element, which happens when someone concatenates two responses into one file. And a document that starts with a byte-order mark or a stray blank line before the <?xml … ?> declaration, which the declaration is not allowed to have in front of it. If none of those, XML Validator reports the position rather than just the verdict.
Related Tools
Worth reading
- XML 1.0, W3C recommendation – Section 2.4 is the one to bookmark: it is where the rule about a bare ampersand actually lives.
- MDN — DOMParser – The browser API doing the parsing here, including how it reports a malformed document.
- RFC 8259 — JSON – What the output conforms to. Section 6 is the reading on why long identifiers are safer as strings.