XML to JSON conversion turns tagged documents into data that JavaScript, REST clients and databases can read directly. You decide how attributes, repeated elements, namespaces and CDATA map to JSON, and your XML stays in your browser.
How do I convert XML to JSON?
Paste your XML into the input box or upload an .xml file, and the JSON appears in the output box as soon as you stop typing. Check the root name and key count above the output, adjust any options, then copy the result, download it as a .json file, or send it to the JSON Formatter. Nothing is uploaded.
How to use this XML to JSON converter
- Add your XML by pasting it, choosing a file or dropping one on the upload box. Load sample XML inserts an example.
- Pick the options: attribute prefix, text key, Auto or Always arrays, namespaces, indent, and optional type inference (off by default).
- Open Advanced options to keep CDATA, comments or the XML declaration, or to turn empty elements into an empty string, null or nothing.
- Copy, Download as .json, Send to JSON Formatter, or Validate against a schema. Results over about 2 MB show a preview, but all four buttons use the whole result.
- Large inputs: live updates pause above about 1 MB, so press Convert. Cancel appears after a second.
Afterwards, the JSON Formatter beautifies and explores the result, the JSON Schema Validator checks its shape, and Compare JSON shows what changed between conversions. For spreadsheet data use CSV to JSON.
What happens to XML attributes in JSON?
JSON has no attributes, so each XML attribute becomes a key with a prefix, @_ by default, which keeps it apart from a child element of the same name. You can change the prefix to @ or _, or ignore attributes. When an element has attributes and text, the text is stored under the key #text, which you can rename.
Why does a single XML element become an object instead of an array?
XML has no array type, so a list is just an element that repeats. A repeated element becomes a JSON array, but an element that appears once stays a single object or string, so a list with one item changes shape. Set Arrays to Always arrays to make every child element an array, even when it appears once.
When to use Always arrays
Always arrays gives your code one shape: every child element is an array, so item[0] works whether the file holds one item or fifty. The root stays an object and attributes are never arrays. The cost is noise, since a plain name becomes a one-item array.
How are CDATA, comments and namespaces handled?
By default CDATA text is merged into the surrounding text, comments and the XML declaration are dropped, and namespace prefixes stay in the key names, such as dc:creator. Advanced options keep CDATA as #cdata, comments as #comment and the declaration as ?xml. Strip prefixes removes namespace prefixes and merges keys that collide into an array.
What happens when stripped prefixes collide
Elements such as a:id and b:id both become id when prefixes are stripped, so they merge into one array and a warning counts the merged keys. If stripping would make two attribute names identical, those attributes keep their prefixes and the warning counts them. Declarations such as xmlns:a are never stripped.
Worked example: attribute, repeated element and CDATA
Both outputs were copied from this converter. The XML has attributes on the root, a repeated item element and a CDATA section; only the Arrays option differs.
XML input
<order id="A-1001" status="shipped">
<customer>Ada Lovelace</customer>
<item sku="K-42">Keyboard</item>
<item sku="M-17">Mouse</item>
<note><![CDATA[Fragile: <handle> with care & smile]]></note>
</order>
Arrays: Auto (default)
{
"order": {
"@_id": "A-1001",
"@_status": "shipped",
"customer": "Ada Lovelace",
"item": [
{
"@_sku": "K-42",
"#text": "Keyboard"
},
{
"@_sku": "M-17",
"#text": "Mouse"
}
],
"note": "Fragile: <handle> with care & smile"
}
}
Arrays: Always arrays
{
"order": {
"@_id": "A-1001",
"@_status": "shipped",
"customer": [
"Ada Lovelace"
],
"item": [
{
"@_sku": "K-42",
"#text": "Keyboard"
},
{
"@_sku": "M-17",
"#text": "Mouse"
}
],
"note": [
"Fragile: <handle> with care & smile"
]
}
}
With Auto, the item elements become an array of objects holding the sku attribute and text, and the CDATA arrives as ordinary text. With Always arrays, customer and note become one-item arrays too, but the attributes stay as they were.
XML to JSON gotchas
There is no single standard for converting XML to JSON. Conventions such as BadgerFish and Parker exist and libraries use their own, so the same XML can give different JSON in different tools. Every example below comes from this converter with default settings unless stated.
Attributes and child elements can look alike
These two documents carry the same information but convert differently:
id as an attribute
<book id="1">
<title>Dune</title>
</book>
{
"book": {
"@_id": "1",
"title": "Dune"
}
}
id as a child element
<book>
<id>1</id>
<title>Dune</title>
</book>
{
"book": {
"id": "1",
"title": "Dune"
}
}
The prefix is part of the key, so code must know which form the source used. Set Attributes to Ignore if you do not need them.
One item is an object, two items are an array
One item, Auto
{
"list": {
"item": "apple"
}
}
Two items, Auto
{
"list": {
"item": [
"apple",
"pear"
]
}
}
One item, Always arrays
{
"list": {
"item": [
"apple"
]
}
}
A parser cannot tell a one-item list from a single value, so the shape of item depends on the data. Always arrays removes that surprise.
Mixed content loses its order
When an element holds text and child elements, JSON cannot record where each piece sat. The text pieces are joined end to end into one #text value.
XML input
<p>Hello <b>bold</b> and <i>italic</i> world.</p>
JSON output
{
"p": {
"#text": "Hello and world.",
"b": "bold",
"i": "italic"
}
}
Hello, and and world. are joined with only the spaces that were already there, so they run together with double spaces, and the position of bold and italic is gone. If order matters, keep the data in XML.
Namespace prefixes and collisions
Two namespaces can reuse a local name. Stripped prefixes make the keys collide and merge:
XML input
<r xmlns:a="urn:a" xmlns:b="urn:b">
<a:id>1</a:id>
<b:id>2</b:id>
</r>
Namespaces: Keep prefixes (default)
{
"r": {
"@_xmlns:a": "urn:a",
"@_xmlns:b": "urn:b",
"a:id": "1",
"b:id": "2"
}
}
Namespaces: Strip prefixes
{
"r": {
"@_xmlns:a": "urn:a",
"@_xmlns:b": "urn:b",
"id": [
"1",
"2"
]
}
}
The warning reads Prefix stripping merged keys: 1. The xmlns declarations keep their names.
Round trip with the JSON Formatter's XML export
The JSON Formatter can export XML and this page can read it back, but not losslessly. A key becomes the root only when an object has one top-level key; several keys, or a top-level array, produce side-by-side elements, so tick Wrap multiple root elements in <root>. Array items are named by dropping a trailing s (tags becomes tag) or adding _item (book becomes book_item), and null is written as the text null. Our guide to converting JSON to CSV, XML and YAML covers the export, and JSON vs XML compares the formats.
Is my XML uploaded anywhere?
No. Your XML never leaves your browser, and nothing you paste is uploaded. Conversion runs locally in a background worker, using a copy of the open-source fast-xml-parser library served from this site. The page uses Google Analytics for anonymous usage counts, which never include your XML, tag names, attribute names or file names.
Uploaded files are read locally too. The Send buttons hand the result over through session storage in your tab, and the receiving page removes it once read.
What are the limitations of this converter?
Values are text unless you turn on type inference, the order of mixed content is lost, and the converter stops at 1,000 levels of nesting. It also refuses output above about 20 MB and files above 100 MB. A DOCTYPE is ignored, so custom entities are never expanded, and a notice tells you when that happens.
Whitespace-only text between child elements is always dropped, even when Trim whitespace is off, because it is indentation. Numeric character references such as A are decoded, invalid ones are rejected with a line and column, and undefined entities such as © stay as written.
Type inference is decided per element path, all or nothing: 007, 1.50, 1e5 and numbers beyond 2^53 keep the whole path as text, and a notice names the path and the first blocking value. That notice appears on the page only; analytics never receive paths, names or values.