XML to JSON Converter_

Convert XML to JSON, and see the mapping instead of guessing it. There is no standard for this conversion — every tool invents its own rules for attributes, namespaces and repeated elements, and applies them silently. This one shows you the rules, lets you change them, and names what your document lost on the way through.

Free, no account, and the file never leaves the page.

toolkit.codes/xml-to-json
JSON
Nothing to convert yet

How to use

  1. 01Paste the XML into the left pane, or pick a file off disk with Upload. A document that is not well-formed is rejected outright, with the position of the first problem — nothing here rewrites your markup to make it parse.
  2. 02Read the panel beside the JSON before you use the output. It names what the mapping dropped and, more usefully, which keys have a shape that depends on how many elements this particular document contained.
  3. 03Set Arrays to "Always arrays" if a program will consume this. It is the difference between output whose shape you can rely on and output that changes when the data does.
  4. 04Choose the attribute prefix and text key your consumer expects. @ and #text match xmltodict and most online converters; $ matches xml2js.
  5. 05Leave Text values on "Keep as strings" unless you know the schema. XML has no types of its own, so inferring them is a guess — and 007 is not 7.

A SOAP response you need to read

An integration returns SOAP and you want to see the payload without wading through envelopes. Note that the namespace prefixes are load-bearing here — soap:Body and the service's own vocabulary are different things — so this is the document where stripping them would cost you.

XML
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><getRateResponse><rate>1.09</rate></getRateResponse></soap:Body></soap:Envelope>
JSON
{
  "soap:Envelope": {
    "@xmlns:soap": "http://schemas.xmlsoap.org/soap/envelope/",
    "soap:Body": {
      "getRateResponse": { "rate": "1.09" }
    }
  }
}

An RSS feed with one item today

The classic array trap, in the wild. This feed has a single <item>, so item is an object. Tomorrow's feed has three and it is an array — the same code, different shape. The report flags it, and Always arrays fixes it.

XML
<rss><channel><item><title>First post</title></item></channel></rss>
JSON (Always arrays)
{
  "rss": [{
    "channel": [{
      "item": [{ "title": ["First post"] }]
    }]
  }]
}

A pom.xml you want to inspect

Maven builds are XML and questions about them are easier to answer in JSON. Everything here is text in the XML, so everything is a string — the version 1.10 would become the number 1.1 if you turned inference on, which is the kind of edit that breaks a build silently.

XML
<project><artifactId>api</artifactId><version>1.10</version></project>
JSON
{
  "project": {
    "artifactId": "api",
    "version": "1.10"
  }
}

A config file that leans on attributes

Attribute-heavy configuration is where the prefix choice earns its keep. With no prefix, the timeout attribute and a timeout child element would land on one key and one would win.

XML
<server host="10.0.0.1" port="8080"><timeout>30</timeout></server>
JSON
{
  "server": {
    "@host": "10.0.0.1",
    "@port": "8080",
    "timeout": "30"
  }
}

One fragment, every mapping option

SettingOutput for the fragmentWhen you want it
Attributes: @ (default){"order":{"@id":"7","item":"A"}}The common convention — xmltodict, and most online converters
Attributes: @_{"order":{"@_id":"7","item":"A"}}Matching fast-xml-parser output
Attributes: ${"order":{"$id":"7","item":"A"}}Matching xml2js output
Attributes: none{"order":{"id":"7","item":"A"}}A flat shape — at the risk of an attribute and a child colliding
Arrays: when repeated (default){"order":{"@id":"7","item":"A"}}Matching what other converters produce
Arrays: always{"order":[{"@id":"7","item":["A"]}]}Anything a program consumes — the shape stops depending on the count
Text key: #text (default)with <item x="1">A</item>: {"item":{"@x":"1","#text":"A"}}The near-universal default
Text key: $t{"item":{"@x":"1","$t":"A"}}Google's GData convention, still seen in older APIs
Namespaces: keep prefix (default)with <n:item>: {"n:item":"A"}Anything where two vocabularies could share a local name
Namespaces: strip prefix{"item":"A"}A single-vocabulary document you want readable — collisions are reported
Text values: keep as strings (default){"item":"7"} for <item>7</item>Always, unless you know the schema. XML has no types of its own
Text values: infer types{"item":7}When the consumer needs real numbers and you have checked for SKUs and versions

Every row is the same input document with one setting changed — an <code>order</code> element carrying an <code>id</code> attribute and a single <code>item</code> child holding the text A. Paste it into the tool above and change the control to watch each row happen.

XML constructs with no JSON equivalent

ConstructWhat it becomes hereWhat to do about it
Attributes vs child elementsAttributes get the configured prefix; children do notKeep a prefix set. With none, an attribute and a child of the same name collide and one wins
CommentsDropped, counted in the reportNothing to do — JSON has no comment syntax. Keep the XML if the comments matter
Processing instructions and the declarationDropped, countedThey instruct an XML processor, and there is not one on the other side
DOCTYPEDropped, countedEntity declarations in it are deliberately not expanded — expanding entities from an untrusted document is how billion-laughs works
Mixed contentText joined under the text key, elements as keys, ordering lostIf order was the data, use an XML query language instead. This is the one genuinely unfaithful case
Document order of differing siblingsInsertion order in the object — preserved in practice, guaranteed by neither specIf order matters to correctness, it belongs in an array
NamespacesPrefix kept in the key, or stripped with collisions reportedKeep them for multi-vocabulary documents like SOAP
CDATAUnwrapped to its textNothing — the content is exact. Only the choice of encoding is gone
Repeated siblingsAn array — but only when they actually repeat, unless Always is setSet Always arrays for anything programmatic
Empty elements"" for both <a/> and <a></a>Nothing — XML gives them identical meaning
EntitiesDecoded: &amp; becomes &Nothing — this is what a JSON consumer wants
Text typesStrings, unless you turn inference onLeave it off unless you know the schema. The schema had the types; the schema is not here

The panel beside the output applies this table to whatever you paste: it names the rows that actually bit, with counts and with the element names involved. Nothing here is a caveat you have to check against your own document by hand.

Tips

  • Set Arrays to "Always arrays" the moment the output is going into code rather than your eyes. Everything else on this page is a preference; that one is a correctness setting.
  • Run the JSON through the JSON formatter if you are going to hand-edit it — this page emits valid JSON, but the formatter is where you check a document you have changed.
  • For a SOAP or multi-namespace document, keep the prefixes. The verbosity is the price of knowing which vocabulary a key came from, and the alternative is a silent collision.
  • If the XML came from a file with a non-UTF-8 declaration, check the accented characters before you trust the output. Pasted text has already been decoded by whatever read it, and no converter can undo a bad read.
  • Converting tabular XML — rows of identical elements — often means you wanted the CSV converter instead, if you can export the same data as CSV. Repeated flat records are what it is built for.
  • When you inherit JSON that came from XML and the keys start with $, it was xml2js. Starting with @_, fast-xml-parser. That tells you which conventions the rest of the document follows.

Gotchas

One item is an object, two are an array

The trap that breaks things in production rather than in testing. Repeated elements become an array; a single occurrence stays an object. So the shape of your JSON is decided by the contents of whichever document you happened to convert. The fixture has one line item and your code does order.item.sku; the first real two-item order arrives and order.item is an array. Nothing failed at conversion time, nothing failed in review, and the eventual error looks like bad data. Set Arrays to "Always arrays", and read the report — it names both the keys that are arrays only because of the count and the ones that would flip if a second element ever appeared.

An attribute and a child element can collide

XML keeps <a b="1"> and <a><b>2</b></a> strictly apart; a JSON object has one namespace of keys. With an attribute prefix set they land on @b and b and both survive. Choose "no prefix" and they both want b, so one silently overwrites the other and the JSON gives no sign that anything was lost. The report names the collision, but the real fix is to keep a prefix unless you have checked the document has no such pair.

Mixed content has no faithful representation

A paragraph with inline markup — text, then an element, then more text — carries meaning in the interleaving. JSON objects have keys, not sequences, so the text gets joined under one key and the elements become other keys, and the fact that one piece of text preceded a child and another followed it is simply gone. Every converter does this; almost none says so. If the document is prose rather than data, converting it to JSON is the wrong operation, and an XML query language will serve you better.

Stripping namespaces can merge two vocabularies

Prefixes are ugly, and dropping them is tempting. In a single-vocabulary document it is harmless. In a document that mixes vocabularies — which is most of the XML that carries namespaces at all, SOAP especially — two different price elements from two different namespaces become one key, and one set of values is gone. Not reordered, not nested: gone. This page reports the collision by name and prefix when it happens, and the answer is to keep the prefixes.

Document order is data in XML and a convention in JSON

An XML document has a defined order for everything in it. A JSON object is, by the letter of its specification, a bag of pairs with no sequence at all. Implementations keep the order you wrote them in, so the output here reads in document order and will keep doing so — but that is a habit of implementations rather than a promise of the format. What puts it at risk is anything downstream that takes the object apart and rebuilds it: a template engine, a serialisation hop, a gateway that normalises payloads. If the order of differing sibling elements is part of what the document means, it needs to be an array, which both specifications do order.

Do not paste a SOAP payload into a server-side converter

The XML people actually convert is integration traffic: SOAP responses, payment gateway callbacks, EDI, financial feeds. It carries account numbers, tokens and personal data as a matter of course. Much of the field for this particular conversion runs the work on a remote server, and at least one of those sites markets a SOAP-specific entry point — an invitation to hand over exactly the documents you should not. Security researchers reported in late 2025 that two of them had been keeping submitted input and serving it back through an unsecured history page. So the question to settle first is where the code executes, for any converter including this one; here it is the tab, and a test in the suite proves no request leaves it.

Technical details

Input
A well-formed XML document. The gate is the same strict scanner that backs the XML formatter, so anything it rejects there is rejected here too, positioned to the character and never rewritten to make it parse.
Mapping
Documented in full, both directions, in the tool spec: attribute prefix, text key, repeated-sibling rule, namespaces, comments, processing instructions, CDATA, entities, empty elements and mixed content.
Reported
Dropped comments, processing instructions and DOCTYPE; CDATA unwrapping; mixed-content ordering loss; keys that are arrays only because of the count; elements that would flip to arrays if they repeated; stripped namespace prefixes and any collision they caused; attribute/child key collisions; what type inference would change or did change; empty elements; a non-UTF-8 declaration.
Types
Text is a string unless inference is enabled. Even then, values a number would corrupt — a leading zero, a trailing zero after a decimal point, a leading plus, anything past the safe integer range — are left as strings and named in the report.
File handling
Uploads are read inside the page with the browser File API and are never transmitted; Download writes out what is already in the tab.
Limits
Input is refused above 4 MB, checked before parsing, because the document becomes a second object graph in memory before it is serialised. The output pane stops drawing past 300,000 characters. Copy and Download are unaffected and always carry the complete result.
Network
None from tool code. A test sweep calls every function this page uses with fetch and XMLHttpRequest replaced by stubs that throw, so a stray request fails the build instead of shipping. Disconnect from the network and the page still works.

Frequently asked questions

Why does my JSON change shape when the XML has more items?

Because the conventional mapping turns repeated sibling elements into an array and leaves a single occurrence as an object, so the shape follows the count. It is the single most common source of bugs from this conversion, and it surfaces in production rather than in testing, because the sample you developed against had one item. Set Arrays to "Always arrays" and the shape stops depending on the data.

Is there a standard way to convert XML to JSON?

No. There is no specification for it, which is why fast-xml-parser, xml2js and xmltodict all produce different output for the same input. The conventions have converged somewhat — an @ prefix for attributes and #text for text content are near-universal — but the differences that remain are real, and a converter that does not tell you which conventions it used is asking to be trusted blindly.

What happens to my comments and the XML declaration?

Both are dropped, and both are counted in the report. JSON has no comment syntax and no processing instructions, so there is nowhere to put them and no way to reconstruct them from the output. If the comments carry information you need, keep the XML as the source of truth.

Why are my numbers strings?

Because XML says they are. An XML document carries no types of its own — the schema has them, and the schema is not in the document you pasted. Turning on inference is available and reports what it changed, but it is off by default because guessing from the characters corrupts data: 007 is a SKU and not the number 7, and version 1.10 is not 1.1. Values like those are left as strings even with inference on.

How do I convert XML to JSON in Python?

xmltodict.parse(text) is the one-liner, and its defaults match this page — @ for attributes, #text for text — so the output is directly comparable. It has the same once-versus-twice array behaviour, and force_list is its answer: pass the element names that should always be lists. The standard library ElementTree gives you a tree but leaves the mapping to you.

What about JavaScript?

fast-xml-parser is the current default: no dependencies, and attributeNamePrefix, textNodeName and isArray cover the same three decisions as the controls here. xml2js is older and still widely deployed, using $ for attributes and _ for text — which is worth knowing when you inherit JSON whose keys start with a dollar sign. In the browser DOMParser gives you a tree and no mapping at all.

Is my XML uploaded anywhere?

No — and it is a fair question to ask of an XML converter specifically, because the XML that gets converted is integration traffic: SOAP responses, gateway callbacks, financial feeds, all of it carrying credentials and account data. Several prominent converters for this conversion run server-side. The work is JavaScript running in this tab. Every function it calls is covered by a test that stubs fetch and XMLHttpRequest to throw, so a request that slipped in would break the build rather than reach a server — and you can confirm it for yourself by disconnecting and carrying on.

Can I convert the JSON back to XML?

The data, yes; the document, not exactly. The mapping is documented in both directions, so a faithful inverse is possible for anything the JSON still holds — but the comments are gone, mixed-content ordering is gone, and the difference between a self-closing and an empty element is gone, because none of them survived the trip out. A JSON to XML converter is queued and will use this same mapping.

UTF-8
Ready
100% LOCAL

There is no standard mapping, so every converter invented one

XML to JSON conversion looks like a solved problem and is not. The two formats do not describe the same shape of data. XML has attributes that are distinct from child elements, namespaces that let two vocabularies share a name, comments, processing instructions, mixed content where text and elements interleave, and a document order that is part of the meaning. JSON offers a much shorter list: named values, ordered lists, and four kinds of leaf. Nothing specifies a translation between the two, so every library and every online converter picked its own conventions.

That is not a problem in itself — the conventions are mostly sensible and mostly similar. The problem is that they are applied without being stated. You paste a document, you get JSON, and nothing tells you that the attribute prefix is @ rather than $, that the comments are gone, that a namespace prefix is now part of a key, or that one of those keys is an array purely because the sample you pasted happened to contain two of something. Every one of those is a decision somebody made for you. This page shows the decisions, and the panel beside the output names the ones that cost your document something.

The once-versus-twice trap

This is the bug that every converter in the field will hand you, and it is worth understanding before you use any of them. Repeated sibling elements are the natural way to write a list in XML, and the natural mapping is a JSON array. But an element that appears only once is not obviously a list, so the conventional rule is: one occurrence becomes an object, two or more become an array.

The result is that the shape of your JSON depends on how many items the document happened to contain. An order with one line item gives you order.item.sku. An order with two gives you order.item[0].sku. Code written against the first works perfectly, passes review, passes its tests — because the fixture, like the sample, had one — and then fails in production on the first multi-item order. The failure is weeks away from the code that caused it and looks like a data problem rather than a conversion problem.

The default here is the conventional rule, because you are usually comparing against or feeding a pipeline that already uses it, and silently producing a different shape would be its own kind of unhelpful. But the report names every key that is an array only because of the count, and every element that appeared exactly once and would flip to an array if a second one ever arrived. Switching Arrays to "Always arrays" makes the shape independent of the sample, which is what you want for anything a program will consume.

Attributes are not child elements, and JSON has only keys

In XML, <item sku="A"/> and <item><sku>A</sku></item> are different documents, and a schema can require one and forbid the other. In JSON both would naturally be {"sku": "A"}, which loses the distinction. The universal answer is to mark attributes with a prefix — @ here by default, @_ in fast-xml-parser, $ in xml2js — so the JSON records which was which.

Choosing "no prefix" is offered because some consumers want the flat shape, but it has a sharp edge: an element with an attribute b and a child element b produces two entries for the same key, and one overwrites the other. That is silent data loss, so the report names the collision when it happens. With any prefix set, the two live at @b and b and both survive.

Namespaces, and what stripping them costs

JSON has no concept of a namespace. The workable options are to keep the prefix as part of the key, which is what this page does by default, or to drop it. Keeping it produces "soap:Body" and "ns2:GetUserResponse", which is ugly to work with and completely unambiguous. Dropping it produces "Body", which is pleasant right up to the moment two vocabularies in the same document both define price.

When that happens the two sets of values land on one key and one of them is gone. It is exactly the kind of loss that a converter should not perform quietly, so stripping is available and every collision it causes is reported by name, with the prefixes that collided. The xmlns declarations themselves are kept as ordinary attributes, because that is what they are in the document — inventing a separate representation for them would be a second undocumented convention on top of the first.

What has no JSON form at all

Comments, processing instructions and the XML declaration are dropped, because JSON has nowhere to put them. Mixed content — text and elements interleaved, as in a paragraph with inline markup — loses its ordering: the text is joined under the text key and the elements become keys, but nothing records that one piece of text came before a child and another after it. If the ordering was the data, as it is in a document-oriented XML format, this conversion is the wrong tool and a proper XML query is the right one.

Two smaller losses are worth naming. A CDATA section keeps its content exactly and loses its wrapper, because a JSON string needs no escape hatch — so <a><![CDATA[<x>]]></a> and <a>&lt;x&gt;</a> produce identical JSON and the output cannot say which you started from. And <a/> and <a></a> both become the empty string, which is correct, because XML gives them identical meaning and the difference is a typing preference rather than data.

In JavaScript, Python, or at the command line

For XML to JSON in JavaScript, fast-xml-parser is the current default choice — it is dependency-free and its options map closely to the controls here, with attributeNamePrefix, textNodeName and isArray covering the same three decisions. The older xml2js is still widely deployed and uses $ for attributes and _ for text, which is why output you inherit may not match what you expect. In the browser, DOMParser gives you a tree but no mapping — you write the walk yourself, and the decisions on this page are exactly the ones you will have to make.

In Python, xmltodict.parse(text) is the one-liner, and its defaults are @ for attributes and #text for text — the same convention this page uses by default, which makes the two directly comparable. It has the same once-versus-twice behaviour, and force_list is its answer to it. The standard library's xml.etree.ElementTree gives you the tree without the mapping, same as DOMParser. At the command line, xq (from the yq project) reads XML and emits JSON with attributes prefixed @, which is the fastest route for a file too big for a browser tab.