XML to JSON converter
Convert XML to JSON or JSON to XML, with @ attributes, #text, arrays decided by the whole document, and the line and column where the XML is not well-formed.
Output will appear here
What this tool does
XML tends to arrive from systems nobody chose: a SOAP response, an RSS feed, a configuration file, an export from something older than JSON. The code that has to use it wants JSON. The reverse is just as common, a JSON payload bound for a system that only accepts XML. This page converts in both directions, in your browser, and reads both with one fixed set of rules, so the two directions agree about what every key means.
What sets it apart is that the shape of the JSON is decided from the whole document rather than from whichever element is being read, and the page says so. Wherever that decision gives an element a different shape from the one it would have had on its own, a note beneath the output names the path and the line and column where it first applied. What JSON has no room for, such as comments or the order of mixed content, is announced the same way instead of vanishing, and in the other direction every rewrite XML forces on your JSON is named at the place it happened.
The rules: @ for an attribute, #text for text
There is one set of rules, and no setting changes it. An attribute becomes a key spelled @ followed by the name of the attribute, and text that has to share its element with anything else goes under the key #text. Neither @ nor # may begin an XML name, so no key written this way can be mistaken for a child element, which is what lets the same JSON be read back into XML without guessing. Read against a small catalog:
- The root element is the one key at the top: a document whose root element is catalog becomes an object with the single key "catalog".
- An element holding nothing but text is that text: <title>Midnight Rain</title> becomes "title": "Midnight Rain".
- An attribute is a key beside the child elements: <book id="bk101"> gives the book "@id": "bk101", and each child element follows as a key, in the order it first appears.
- An element with attributes and text keeps the text under #text: <price currency="USD">44.95</price> becomes "price": {"@currency": "USD", "#text": 44.95}.
- An element that repeats under one parent becomes an array: a book whose author element appears twice has "author": ["Gambardella, Matthew", "Knorr, Stefan"].
- Names are kept exactly as written, prefix and all: <dc:title> becomes the key "dc:title", a namespace declaration becomes an attribute key such as "@xmlns:dc", and nothing is resolved to a namespace URI.
Entities and character references are decoded, so & arrives as & and é as é, and a CDATA section is simply text. White space standing alone between elements is layout and is dropped; every other run of text is kept in full and never trimmed. The two normalisations every conforming XML reader applies still come first: a carriage return typed into text arrives as a line feed, and a tab or a line break typed into an attribute value arrives as a space. A character reference is how a document keeps either one.
Why the whole document decides the shape
A converter that decides one element at a time writes the authors of the first book as an array, because there are two of them, and the author of the second as a string, because there is one. An element that carries an attribute is an object in one record and plain text in the next. Code written against the first record breaks on the second, and nothing along the way said that the shape could change.
So the shape is decided per path: the element names from the root down to an element, written /catalog/book/author, with no positions in it, so that every author of every book stands on one path. Every element on a path is weighed at once:
- Arrays. If the element repeats under any one parent, every element on the path is written as an array, a lone one included.
- Objects. If any element on the path carries an attribute or a child element, every element on it is written as an object, one that holds only text included, and keeps its text under #text.
- Values. A path is read as numbers only where every value on it converts, and as booleans only where every value is true or false, by the rule described below under numbers and booleans.
- Empty elements. An empty element takes no part in these decisions, and takes the kind its path settles on.
Wherever the result differs from what an element would have been on its own, the page says so once for the path, with the line and column of the first element it changed. In a catalog where one book has several authors and another has only one, that single author is still an array; where one price carries a currency attribute and another does not, the plain price is still an object, its text under #text. Both are announced, so neither surprises the code that reads them.
A sample with one record, and the Always arrays field
The shape can only be as good as the document it is decided from. Paste a catalog holding a single book and that book is an object, its only author a string and its price a number, where the same catalog with a second book beside it makes the books an array. A path that appears once everywhere in what you pasted stays a single value, and a path whose values all happen to convert stays numbers until a document arrives holding a ZIP code that opens on a zero. The JSON is exactly as dependable as the sample is representative, so paste more than one record whenever you can.
For arrays there is a remedy that does not depend on the sample. Type into Always arrays the element names or paths that must be arrays whatever the document shows, separated by commas or spaces, and every path an entry reaches is written as an array everywhere, even where it appears only once:
- A bare name such as item reaches every path that ends in an element named item, wherever it stands.
- A path such as /rss/channel/item reaches that one path, written the way the paths beneath the output are written, so one can be copied from there.
- Matching is exact: case counts, and a prefix is part of the name, so Item reaches no item and link reaches no atom:link.
- Each path the field turned into an array is announced like any other decision. An entry that is neither a name nor a path, and one that reached no element in the document, is listed under the field, and the entries that do apply keep applying.
The field belongs to the XML to JSON direction. It keeps what you typed when you switch the direction and back, and like every setting on the page it is not kept between visits. Values have no such field: a path that must stay text whatever the sample holds is what turning off Convert numbers and booleans is for, at the price of every other value becoming text as well.
Numbers, booleans, and why 02134 stays text
Everything in XML is text, and turning text that looks numeric into JSON numbers is usually what you want. It is also how a ZIP code of 02134 becomes 2134, and how a long order number loses its last digits. The rule here is the one the CSV converter on this site uses, and it needs no list of special cases: text becomes a number only where that number, written back out, spells the text exactly. 42 does, so it converts. 02134 does not, and neither do 1.50, +5, 1e5 or an integer too long for a double to hold every digit of. The booleans are exactly true and false, in lower case.
What this page adds is that the rule is asked of a whole path at once. A path is read as numbers only if every value on it converts, and as booleans only if every value is one; a single value that would lose a digit, or numbers and booleans meeting on one path, keeps every value there as text. Each attribute name on a path is decided on its own. A path kept as text although some of its values would have converted is announced, naming the path, and for an attribute the path ends on the attribute, as in /catalog/book/@id.
Convert numbers and booleans is on when the page opens, as the matching switch is on the CSV page, and turning it off writes every value as text. The value of a namespace declaration is never read as a number either way, because the Namespaces in XML standard defines that value as a namespace name, which is text.
Empty elements, and the null a document states itself
- An empty element takes the kind its path settles on. On a path of text it is the empty string. On a path of numbers or booleans it is null, and the page says so, since on its own the element would have been empty text.
- Empty means nothing at all: <zip> </zip> holds a space, and a space is text.
- A path on which every element is empty is text, so each of them is the empty string. Nothing there reads as a number, and a null would be information the document never gave.
- On a path written as objects, an empty element carries #text as well, empty or null by the same rule, wherever another element on the path holds text: beside <price currency="USD">5</price>, a <price/> becomes {"#text": null}. Where no element on the path holds text, as with image elements that carry only a src attribute, none of them carries #text.
A document can also state null itself. An element marked xsi:nil="true", with that prefix bound to the XML Schema instance namespace, is null on any path, whatever Convert numbers and booleans is set to. The marking is how the document says null rather than data, so neither the attribute nor a namespace declaration that binds nothing else appears in the JSON. Any prefix bound to that namespace works, and i:nil="1" says the same thing; xsi:nil="false" is ordinary data, and an element marked nil that still holds content is left as it stands, since reading it as null would drop that content without a word.
What JSON has no room for, announced instead of lost
Some of what an XML document holds has no place in JSON. What carries meaning is announced beneath the output rather than dropped in silence:
- The order of mixed content. In <p>Hello <b>world</b>, again</p> the text is gathered under #text as "Hello , again" and the b element under "b": every character and every element survives, but not the order they stood in. Children that alternate between names lose their order the same way. It is announced once for the path of the element whose content it is, and only where order really was lost, so <p><b>Note:</b> the rest</p> raises nothing, the order of its keys keeping it.
- Comments and processing instructions. They are left out, with one announcement for each kind that says how many there were and where the first one stands.
- The DOCTYPE. It is left out as well, and its announcement says the DTD was not applied: a default value the DTD declares for an attribute is missing from the JSON, because this tool does not read the DTD.
- The XML declaration at the top. It is left out with no announcement, because it describes how the text is encoded rather than what the data is.
JSON back to XML: the nearest XML, and each rewrite named
Switch the direction and the same rules run the other way: an @ key becomes an attribute, #text becomes the text of its element, an array becomes one element for each member, and any other key becomes a child element, the child elements in the order their keys stand. A null is written as an empty element marked xsi:nil="true", with the XML Schema instance namespace declared once on the root element and only when a null is written, so converting back gives null again rather than an empty string. Apart from JSON that does not parse, or that nests too deeply to convert, nothing is refused. Where your JSON has a shape XML cannot carry as it stands, the nearest well-formed XML is written and the rewrite is named beneath the output, at a JSONPath you can paste into the JSONPath tool on this site:
- Anything other than a single root element, whether several keys at the top, none at all, a top-level array or a bare value, is written inside <root>.
- A key that is not an XML name is written as the nearest name that no other key beside it is written as: first name as first_name, and 1st as _1st.
- A member of an array that no key names, whether it stands inside another array or at the top, is written as an <item> element.
- An @ key or a #text holding an object or an array is written as a child element, since neither an attribute nor a text can hold one.
- An @ key holding null is left out, and so is a #text holding null beside child elements, since XML says null only of a whole element.
- A character that XML 1.0 cannot contain at all, not even as a reference, is replaced, and the page says where.
Escaping is part of writing well-formed XML, not an option. A < and an & are always escaped. In an attribute value, so is a double quote, because the value is written between two of them; a tab, a line feed or a carriage return there is written as a character reference, because a reader would otherwise turn it into a space. In text, a carriage return is written as a reference, because a reader would otherwise turn it into a line feed, and ]]> is never written as it stands. That is what lets the value read back as the value you wrote.
What converting back gives, said before you convert
Some XML is well-formed and still reads back as something other than the JSON it was written from, so the page reads the XML it has just written, by the same rules the other direction uses, and says where the answer differs. An array of one reads back as its only member, unless the same elements repeat under one parent somewhere else in the document. An empty array writes no element and an empty #text writes no text, so each reads back as nothing. An object holding nothing that is written as an attribute or a child element reads back as a plain value, and where the same elements elsewhere make the document decide an array or an object your JSON did not have, that is said as well. Each of these is written as your JSON has it, and announced.
Use as input moves the output into the input box and switches the direction, so what comes back, and what it announces, is one click away. Where nothing is announced, what comes back is your JSON, apart from what XML does not keep. XML text does not record whether a value was a number, a boolean or a string: with Convert numbers and booleans on, a string that spells a number exactly can come back as that number, and with it off every value comes back as text. And an attribute, or an empty or null #text, can come back in a different place among the keys beside it, because XML does not keep that position.
Going round from the XML side promises less, on purpose. The text you pasted does not come back: the declaration, the layout, the comments and everything else announced on the way out are gone. What stays steady is the XML the page writes: convert it to JSON and back again, with the same settings, and the same XML comes back.
When the XML or the JSON does not read
A document the XML reader cannot read is refused with a sentence saying what is wrong, and a line and column wherever there is a place to give: an element that is never closed, an attribute value with no quotes and an & that begins no reference each have a sentence of their own. A closing tag that does not match the element still open says so, then shows the closing tag that element needs, which names the element left open. JSON that does not parse is refused the same way, with the line and column where its parser stopped wherever that can be measured.
Beneath a sentence that gives a place, the page shows your own line with the failing character marked, which matters most for minified XML or JSON, where the whole document is a single line. A long line is cut down to the stretch around the mark, with an ellipsis where it was cut. A failing character that draws nothing, such as the end of the text, a tab or a byte order mark, is marked on a visible stand-in. The column counts the way JavaScript counts a string, in UTF-16 code units, so an emoji or a rare ideograph before the mark moves it on by two rather than by one.
A document nested more deeply than the site allows is refused too, in either direction, and its sentence states the limit.
Frequently asked questions
- Why is a single element written as an array?
- Because an element on the same path repeats under one parent somewhere else in the document, and the path is decided once for the whole of it. Code that reads the value as an array then works for every record, including those with a single element. The page names the path, with the line and column of the first element this changed. To get an array where your sample holds only one, name the element in Always arrays.
- Why is a number in quotes in my JSON?
- Because some value on the same path would not survive as a number, whether through a leading zero, a trailing zero after the decimal point, a plus sign or an exponent, or because numbers and booleans meet on that path. Such a path is text everywhere, and the page names it. Its values convert once every one of them is written the way the number is written back out, 2134 rather than 02134.
- What happens to comments, processing instructions and the DOCTYPE?
- They are left out of the JSON, and each kind is announced with how many there were and where the first one stands. The announcement for the DOCTYPE also says the DTD was not applied, so any default attribute value it declares is absent.
- Does it check my XML against a DTD or an XSD?
- No. It reads well-formed XML and says where it could not read a document, but it does not validate against a DTD or an XSD, and it applies nothing a DTD declares. That is why a DOCTYPE is announced as not applied rather than quietly ignored.
- How is null written in XML, and does it come back?
- As an empty element marked xsi:nil="true", with the XML Schema instance namespace declared once on the root element. Converting that XML back gives null again, where a plain empty element would have come back as an empty string on any path read as text. If your JSON already uses the prefix xsi for a name of its own, the page declares a free prefix instead.
- What happens to a key with a space in it?
- It is written as the nearest name XML allows, so first name becomes first_name, and the page names the key by its JSONPath together with the name it became. Paste that JSONPath into the JSONPath tool on this site to select every value the rewrite applied to.
- Why does converting back not give me my original XML?
- Because JSON has no place for part of what an XML file holds: the declaration, the layout between elements, comments, processing instructions, the DOCTYPE and the order of mixed content. What carried meaning was announced on the way out. What stays steady is the XML the page writes: take it to JSON and back again, with the same settings, and the same XML comes back.
- Is anything I paste sent to a server?
- No. Both directions run entirely in your browser, so the document you paste, whether an API response, a feed or a configuration file with credentials in it, never leaves your device.
Related tools
- JSON formatter
Validate and beautify JSON, with clear error locations.
- SQL formatter
Format and beautify SQL — multiple dialects.
- XML formatter
Format XML and check it is well-formed — beautify or minify.
- CSV to JSON converter
Convert CSV to JSON and back — quoting handled properly.