JSON to XML Converter_
Transform JSON to XML, and see what XML made you add. Nothing in your JSON is lost going this way — but XML insists on things JSON never asked for: a single root element, names that obey a grammar, and no concept of null. This page makes those additions rather than hiding them, and lists every one.
Free, no account, and the file never leaves the page.
What XML required
XML is a stricter target than JSON is a source
Going the other way — XML into JSON — the difficulty is that XML holds things JSON has no room for, so a conversion has to drop some of them. This direction has the opposite shape and it catches people by surprise, because "JSON is simpler" makes it sound like the easy way round. It is not. Everything in your JSON survives the trip intact. The problem is that XML will not accept several perfectly ordinary JSON documents without changes, so the conversion has to invent structure that was never in your data.
There are three of these, and every converter deals with all three. Almost none of them says so. You paste a document, you get XML, and nothing tells you that a wrapper element appeared at the top, that a field was renamed because its name broke a rule you have never had to think about, or that a null became something a reader cannot distinguish from an empty string. Each of those is an edit to your data performed on your behalf. The panel beside the output lists them.
One root element, and what that costs an array
An XML document has exactly one outermost element. That is not a stylistic preference, it is the definition — a document with two top-level elements is not XML and every parser will reject it. JSON has no such rule: an array is a complete document, so is a number, and an object may have as many top-level keys as it likes.
So [1, 2, 3] cannot become three elements side by side. It has to be wrapped, and the wrapper needs a name that was nowhere in your input. This page uses root by default and lets you rename it, because the right name depends entirely on what the receiving system expects and no converter can guess it. The same applies to an object with several top-level keys, and to a bare 42.
The one case that needs no wrapper is a JSON object with exactly one key: that key already is the outermost thing, so it becomes the root element directly and the document round-trips cleanly. If you control the shape of the JSON and you want XML back out of it later, that is the shape to aim for.
Names JSON allows and XML forbids
A JSON key is a string. Any string: "first name", "123id", "user:id", "", an emoji. An XML element name must begin with a letter or an underscore and may then contain only letters, digits, dots, hyphens and underscores — a colon is reserved for namespace prefixes and a leading digit is illegal outright.
Every converter handles this the same way, by rewriting the name: spaces become underscores, a leading digit gets an underscore in front of it. This page follows the same rules so its output is interchangeable with everyone else's — the difference is that it tells you. A field called first name arriving at its destination as first_name is a renamed field, and if the consumer looks it up by name it will not find it. That belongs in front of you, not in a footnote.
There is one case where renaming is not good enough. If a document has both first name and first_name as siblings, they rewrite to the same element name — two fields becoming one, with one of them lost. That is not tidying, and no warning is adequate, so the conversion refuses and names both keys. It is the only input this page turns away that is otherwise valid, and it does so because emitting it would be silent data loss.
Attributes are a convention, not a recovery
XML distinguishes an attribute from a child element; JSON has only keys. The established workaround is a naming convention: mark attributes with a prefix, usually @, and a converter going either way can honour it. The reverse direction writes attributes that way by default.
Which makes turning @-prefixed keys back into attributes the obvious default here — and it is the wrong one, for a reason worth knowing. JSON-LD, the linked-data format behind a large share of structured data on the web, uses @context, @id, @type and @graph as its own vocabulary. Those are not attributes and never were. A converter that assumes otherwise silently mangles every JSON-LD document handed to it.
So attribute reconstruction is off unless you turn it on, and the report tells you the control exists whenever it spots prefixed keys — including a specific warning when the document looks like JSON-LD. When you do enable it, every key that became an attribute is listed, because nothing in a JSON document actually says a field was an attribute. The prefix says it was named a certain way. That is a convention you are choosing to trust, not information being recovered.
The switch carries one more key with it. An element that has attributes and its own text cannot put that text under a name, so the convention parks it at #text — and honouring the prefix while ignoring that would rebuild <note id="7">hello</note> as <note id="7"><_text>hello</_text></note>: well-formed, and not the document that went in. So with Attributes on, #text supplies the element's text. Only that spelling, though. _text and $t are used by other converters and are also things a person may simply have called a field, whereas # cannot begin an XML name and so never appears by accident.
Null, and the three things that look alike
XML has no null. A missing value is either an absent element or an empty one, and there is no third option without inventing a convention. This page emits <field/>, which is honest and lossy in a specific way: an empty object and an empty array produce exactly the same thing, so three distinct JSON values converge on one XML shape and nothing downstream can tell them apart.
The schema-aware alternative is xsi:nil="true", which requires declaring the XML Schema instance namespace on the document. That was considered and left out: it changes the shape of the output for everybody in order to serve the minority whose consumer honours it, and a consumer that does honour it will also have a schema saying so, at which point you want a proper serialiser rather than a browser tab. The report names how many nulls and how many empty collections were flattened, which is the information you need to decide whether it matters.
In JavaScript, Python or Java
In JavaScript, fast-xml-parser ships a XMLBuilder alongside its parser, and its options line up with the controls here — attributeNamePrefix, format, indentBy. It will not sanitise element names for you, so a key with a space produces output that fails to parse; validate what you build. The older xml2js has a Builder with rootName and headless options covering the root and the declaration.
In Python, xmltodict.unparse is the counterpart to the parse everyone knows, and it takes a dictionary with exactly one top-level key — it raises if you hand it an array or several keys, which is that one-root rule showing up as an exception rather than a wrapper. dicttoxml is more forgiving and adds its own type attributes by default, which surprises people who did not ask for them. In Java, Jackson's XmlMapper is the standard route and the @JacksonXmlRootElement annotation is how you name the root; without it you get the class name.
How to use
- 01Put your document in the left pane — paste it, or load a
.jsonfile with Upload, which reads it locally. - 02Set the root element name to whatever the receiving system expects. It only matters when your JSON is an array, a bare value, or an object with several top-level keys — a single-key object supplies its own root.
- 03Read the panel beside the XML. Everything it lists is something the conversion added or changed, because XML required it — a wrapper, a renamed field, a flattened null.
- 04Leave Attributes off unless this JSON came from XML. If the document uses @context or @id it is JSON-LD, and those are vocabulary rather than attributes.
- 05Take the XML with Copy, or save it as converted.xml. Every document this page emits is parsed before it is shown, so what you copy is well-formed.
A config a Java service expects as XML
The service reads XML and everything upstream produces JSON. A single-key object is the shape to aim for here — it supplies its own root, so no wrapper is invented and nothing is added that the consumer has to know about.
{"datasource": {"url": "jdbc:postgresql://db:5432/app", "poolSize": 12}}<?xml version="1.0" encoding="UTF-8"?> <datasource> <url>jdbc:postgresql://db:5432/app</url> <poolSize>12</poolSize> </datasource>
A list that has to become a document
An API returned a bare array and the destination needs XML. There is no way to express this without a wrapper — XML has exactly one root — so the wrapper is named and reported rather than slipped in.
[{"sku": "A1"}, {"sku": "B2"}]<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<sku>A1</sku>
</item>
<item>
<sku>B2</sku>
</item>
</root>Rebuilding a SOAP body from JSON
You have the payload as JSON and need it back inside an envelope. Attributes on: the @-prefixed keys become real attributes, which is what the endpoint is expecting.
{"getRate": {"@currency": "EUR", "amount": "100"}}<?xml version="1.0" encoding="UTF-8"?> <getRate currency="EUR"> <amount>100</amount> </getRate>
An export whose field names came from a spreadsheet
Column headings became JSON keys, and headings have spaces in them. Every rename is listed, so you can tell the receiving team that Order Date now arrives as Order_Date.
{"row": {"Order Date": "2026-07-31", "2nd contact": "ada"}}<?xml version="1.0" encoding="UTF-8"?> <row> <Order_Date>2026-07-31</Order_Date> <_2nd_contact>ada</_2nd_contact> </row>
What each JSON construct becomes
| JSON | XML here | Note |
|---|---|---|
| An object with one key | that key becomes the root element | The only shape needing no wrapper — and the only one that round-trips exactly |
| An object with several keys | wrapped in <root> | Reported. The wrapper is not in your data |
| An array at the root | <root><item>…</item></root> | Reported. XML has exactly one root element |
| A bare number, string or boolean | <root>42</root> | Reported |
| An array as a value | the key repeated once per element | {"b":[1,2]} → <b>1</b><b>2</b> |
| An array inside an array | the inner array wrapped in an item element | Without the extra level the nesting would flatten |
null | <field/> | Reported. XML has no null |
{} and [] | <field/> | Reported — the same shape as null. XML cannot tell the three apart |
| A number or boolean value | its text | XML has no types of its own; the schema would have them |
A value containing & or < | escaped to & / < | Reported. The text is unchanged — a parser hands your consumer the original |
A key starting with @ | an element, unless Attributes is on | Off by default because JSON-LD uses @context and @id as vocabulary |
| A prefixed key holding an object | stays an element even with Attributes on | An XML attribute value is text, so a structure cannot be one |
"#text" | the element's own text, with Attributes on | The other half of the same convention. _text and $t are not honoured — those are names a person might really use |
| A number past 2^53 | already rounded by JSON.parse | Reported. XML would have carried it exactly — the loss is on the JSON side |
| Unicode and emoji values | unchanged | XML is Unicode throughout |
Defaults throughout: root element root, array items item, attributes off, declaration on. Every row is covered by a test that parses the output back and fails if it is not well-formed.
What makes a JSON key illegal as an element name
| The key | Becomes | Why |
|---|---|---|
"first name" | <first_name> | A space cannot appear in a name. Each run of illegal characters collapses to one underscore |
"123id" | <_123id> | Digits are legal inside a name and illegal at the start |
"-x" and ".x" | <_-x>, <_.x> | Same rule: legal inside, illegal leading |
"user:id" | <user_id> | A colon means a namespace prefix. Keeping it would claim a namespace the document never declared |
"a<b" | <a_b> | Angle brackets end a tag; they cannot be in a name |
"" | <_> | An empty name is not a name. A bare underscore is legal and obviously a placeholder — deliberately not the array-item name, which would read as a list entry |
"💡" | <_> | Nothing legal survives, so there is nothing to sanitise into. Two such keys as siblings collide and are refused |
"dots.are.legal" | unchanged | Dots, hyphens and underscores are all legal inside a name |
| Two keys, one result | refused | "a b" and "a_b" as siblings both give <a_b>. Two fields would merge and one would be lost, so the conversion stops and names both |
An XML name starts with a letter or underscore, then allows letters, digits, dots, hyphens and underscores. Every rewrite below is listed in the panel beside the output, with the original name and the new one.
Tips
- Aim for a single-key object when you control the JSON. It is the one shape that needs no wrapper, so nothing is added and converting back returns what you started with.
- Name the root after what the consumer expects rather than leaving it as
root. The default is a placeholder, and a schema-validating endpoint will reject it. - Check the rename list before you hand the output to another team. A field that arrives under a different name is the kind of thing that gets diagnosed as a network problem for a day.
- Take a document you are unsure of to the JSON formatter before converting it. What you get here on a syntax error is one position and a diagnosis; what you want while repairing a file is the whole thing laid out.
- Turn the declaration off when the output is a fragment being pasted inside a larger document. An XML declaration is only legal at the very start of a document, so a fragment carrying one will not parse where you put it.
- Reach for
xmltodict.unparsein Python once a document outgrows what a tab should hold. Note that it insists on a dictionary with exactly one top-level key — the one-root rule again, raised as an exception rather than answered with a wrapper.
Gotchas
XML has one root, so an array needs a wrapper that is not your data
A JSON array is a complete document. An XML document with three top-level elements is not a document at all — parsers reject it outright. So converting [1, 2, 3] requires inventing a container element, and whatever it is called, that name appears in the output without appearing in the input. If the result is round-tripped later, that container comes back as a JSON key nobody wrote. Name it after something the consumer expects, and remember it is there.
Sanitisation renames fields that consumers look up by name
JSON keys can be any string; XML element names cannot. So first name becomes first_name, 123id becomes _123id, and every converter in the field does this without comment. The output looks perfectly correct — it is well-formed XML with sensible names — and the field a downstream system searches for by its exact name is no longer called that. Read the rename list. And note the one case that is refused rather than reported: two sibling keys that sanitise to the same name would merge into one element, losing a field, which no warning is loud enough to cover.
Attribute reconstruction is a convention you choose, not information recovered
Nothing in a JSON document records that a value was once an XML attribute. The @ prefix is a convention that converters agreed on, so turning those keys back into attributes is a decision you are making about what the prefix means. It is off by default here specifically because JSON-LD uses @context, @id, @type and @graph as core vocabulary — a converter that assumes the prefix means "attribute" silently rewrites the structure of every piece of linked data it is given.
Null has no XML form, and neither do empty collections
XML offers an empty element and nothing else, so null, {} and [] all become <field/> and the difference between them is gone. Converting back gives you an empty string for all three. If your consumer distinguishes a null from an absent value from an empty list, XML alone cannot carry that and the convention has to come from a schema — xsi:nil is the usual one, and it needs a namespace declaration this page deliberately does not add on everyone's behalf.
Do not paste a payload into a server-side converter
What gets converted in this direction is usually an outbound message: a SOAP body being assembled, an export for a system that predates JSON, a filing an authority accepts only as XML. Those payloads carry the identifiers and secrets the receiving system authenticates on. Two of the five most visible tools for this conversion post the document to their own backend, and one of them keeps a saved-document feature whose own documentation says unsaved-without-login content becomes public. Settle where the code executes before the paste, not after — on this page it is your own tab, and the test suite fails the build if anything tries to call out.
Technical details
- Input
- JSON of any shape the grammar permits at the top level: an object, an array, a quoted string, a number, a boolean, or
nullalone. Syntax errors come back with a line, a column and a guess at the cause. - Output
- Well-formed XML, and that is asserted rather than claimed — each test in the suite feeds its own output back into the strict scanner behind the XML formatter and fails on any objection.
- Mapping
- The inverse of the one documented for the XML to JSON direction, in the same spec, so a document converted one way and back uses one vocabulary throughout.
- Reported
- The root wrapper and why it was needed, every renamed key with its before and after, nulls and empty collections flattened to empty elements, keys reconstructed as attributes, prefixed keys left as elements with a JSON-LD warning, values that required escaping, and precision already lost by
JSON.parse. - Refused
- Two sibling keys that sanitise to the same element name. Emitting that merges two fields into one and loses one of them, so the conversion stops and names both keys.
- 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
- Anything over 4 MB is turned away on sight, before a single byte is parsed — an element tree has to exist in memory alongside the source for the whole of the serialisation. 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
fetchandXMLHttpRequestreplaced 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 did my JSON gain a <root> element?
Because XML documents have exactly one outermost element and your JSON did not supply one. An array, a bare value, and an object with several top-level keys all need a container, so one is created and named. The exception is an object with a single key: that key becomes the root directly and nothing is invented. If you control the JSON, that is the shape worth producing.
Can I choose the root element name?
Yes, and you generally should. The default is a placeholder, and a schema-validating consumer will reject a document whose root is literally called root. The array item name is configurable for the same reason — an RSS feed wants item, an order wants line, and no converter can guess which.
Why was my field renamed?
Because its name is legal in JSON and illegal in XML. Element names must start with a letter or underscore and may then contain only letters, digits, dots, hyphens and underscores, so a space becomes an underscore and a leading digit gains one. Every converter does this; this one lists each rename with the original name, because a field that arrives under a different name is very hard to diagnose from the far end.
Why did the conversion refuse instead of just renaming?
Because two sibling keys sanitised to the same element name. A document with both "first name" and "first_name" produces one element for two fields, and one of the values is simply gone. That is data loss rather than tidying, and no report is prominent enough to make it safe, so the conversion stops and names both keys. Rename one of them in the JSON and it will convert.
Why are my @-prefixed keys not attributes?
Because that behaviour is off by default, and deliberately. The @ prefix is the convention converters use for attributes, so reconstructing them looks obvious — but JSON-LD uses @context, @id, @type and @graph as its own vocabulary, and treating those as XML attributes mangles the document. Switch Attributes on when you know the JSON came from XML; leave it off for linked data.
What happens to null?
It becomes an empty element, and so do an empty object and an empty array — XML has no null and offers no way to tell the three apart. Converting back gives an empty string for all of them. The report counts how many of each were flattened. The schema-aware answer is xsi:nil, which needs a namespace declaration added to your document, so this page does not add it on your behalf.
How do I convert JSON to XML in Python?
xmltodict.unparse(data) is the direct counterpart to the parse everyone uses, and it requires a dictionary with exactly one top-level key — it raises otherwise, which is the one-root rule appearing as an exception rather than a wrapper. dicttoxml is more permissive but adds type attributes by default that you did not ask for. In Java, Jackson XmlMapper is the standard route.
Does my JSON leave the browser?
No. 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.
Is the output guaranteed to be valid XML?
It is guaranteed to be well-formed, which is the part a converter can promise: every test in the suite parses the output it produced back through the same strict scanner the XML formatter uses, and fails if it objects. Validity against a particular schema is a different question and depends on the schema — the root name and element names would have to match what it declares.