Free & Private Online Browser Utility

JSON to XML Converter (Free, No Upload)

This page takes a JSON document and writes an XML tree, and it shows its work while doing it. The two formats do not describe the same world, so four decisions have to be made before a single tag appears: what an element is named when the key holding it uses characters XML forbids, whether a list becomes repeated siblings or a wrapper of named items, where a value that JSON calls null goes in a format that has no such word, and whether any key should stop being a child and become an attribute of its parent. Each one is a control you can see rather than a preference the page keeps to itself, and each one produces a line in the report above the output when it changes your document. Element names are repaired rather than discarded, and the pairs are listed, so a key that opened with a digit arrives as an underscore-prefixed tag you can still trace back. The escaping is the part with no opinion: ampersands, angle brackets and, inside attribute values, quotes plus tab, newline and carriage return all become character references, because a parser normalises raw whitespace in an attribute and would hand your reader a space where your data had a line break. Nothing is uploaded. The text is parsed where you typed it, the document is assembled in this tab, and the file you download comes from the same string the pane displays.

How to use

  1. Step 1: Paste the payload or open a .json file

    Type or drop in the document, or press Load .json and pick it from disk. The file is read into this tab and never sent anywhere.

  2. Step 2: Set the shape your reader expects

    Name the root element or switch the wrapper off, choose repeated siblings or named items for arrays, and decide whether keys beginning with the attribute prefix should become attributes. The document rebuilds as you change each control.

  3. Step 3: Read the report, then take the file away

    The lines above the pane list renames, empty-array omissions, collapsed nulls and demoted attributes. Copy the text or download the .xml once nothing in that list surprises you.

Options explained

Root element name and wrapper

Puts the whole tree inside one element called what you type, defaulting to root; switching it off makes your own top-level keys the document elements.

When to use: Keep the wrapper unless a consumer addresses children of a specific element by name. Turn it off only for objects with a single top-level key, because two of them are refused rather than emitted broken.

Array style

Repeats the key once per item, or writes one wrapper containing an item element whose name you set.

When to use: Repetition is the idiomatic XML list and what most readers parse. The wrapper suits a schema that names the collection and its members differently, and it is the only style that keeps an empty array's name in the document.

Attribute prefix

Turns a primitive value under a key starting with @ or - into an attribute of its parent element instead of a child element.

When to use: Use it for metadata that belongs on the tag, such as identifiers or units, and leave it off when your JSON was never written with a prefix, since nothing then matches and every key stays an element.

Indentation and declaration

Two spaces, four, a tab or no line breaks at all, plus the optional <?xml?> line at the top.

When to use: Single line is what you embed in a request body; the declaration is worth keeping when a file might be opened by something that cannot assume UTF-8.

Loss report

Counts elements and attributes in the emitted document, lists every rename and collision, names empty arrays that wrote nothing, and totals the nulls, empty strings and forbidden characters that collapsed or vanished.

When to use: Read it before a converted file goes to anything that consumes XML by name. It is where an unexpected empty element stops being a mystery in someone else's stack trace.

Worked example

Input: { "@ref": "S-8", "receiver": "Anna Lindqvist", "parcels": [ { "sku": "T-2", "count": 2 }, { "sku": "T-9", "count": 1 } ], "signature": null }

Output: <?xml version="1.0" encoding="UTF-8"?> <root> <_ref>S-8</_ref> <receiver>Anna Lindqvist</receiver> <parcels> <sku>T-2</sku> <count>2</count> </parcels> <parcels> <sku>T-9</sku> <count>1</count> </parcels> <signature/> </root>

The list became two siblings carrying its own key, the null became an empty element, and the key opening with @ kept its value but lost a character XML will not put first in a name — which is why the report names that rename instead of leaving you to spot it.

Tips & limits

  • Set the root name to whatever the consuming reader matches on before you convert, not after: an XPath or a binding written against another element name fails silently, and the document itself is not the place to discover it.
  • When the report shows an empty array that wrote nothing, add the key back on purpose — the wrap style, or an explicit null in the JSON — rather than explaining to a downstream reader why a field is absent.
  • Paste the finished document into an XML parser you trust before it reaches production. The characters and names are the part this page controls; whether your schema is satisfied is a question only a validator that knows the schema can answer.

Common problems

The output repeats one element name instead of wrapping the list

That is the default array style, which spells a list as siblings sharing the key. Choose the wrapper style and the items take the name you type, one level inside the collection element.

A message says the document writes two top-level elements

XML permits one root. Either switch the wrapper back on, or move the values under a single key so the tree has one top.

Names in the output have underscores where the JSON had spaces, dashes or digits

Those characters cannot appear in an XML name, or cannot start one, so they are repaired and the pairs are printed above the pane. Rename the keys in the source if the reader is strict about them.

Frequently Asked Questions (FAQ)

Why does my key that starts with a number get an underscore in front of it?

An XML name has to open on a letter or an underscore, so a key like 2024-revenue cannot become a tag on its own. The prefix is added instead of the illegal character being deleted, which is why the result is _2024-revenue rather than _024-revenue: a digit is legal inside a name, and whoever reads the output should still be able to tell which key it came from. The rename appears in the report as a pair.

What happens to null?

XML 1.0 has no null. An empty element is the closest thing to it, and that is what gets written, which means null and the empty string produce the same tag and a reader of that document gets back an empty string where the JSON had a deliberate absence. Both cases are counted so the collapse is visible at conversion time instead of surfacing as a bug later.

Can a JSON key become an XML attribute?

Yes, when the prefix option is switched on and the key starts with that character and holds a primitive. An attribute cannot contain structure, so an object or an array under the prefix is demoted to a child element and named in the report; two keys that would produce the same attribute name are resolved the same way, with the later one going to an element.

Why are the elements in a different order from my JSON?

Object keys in JavaScript are enumerated with integer-like keys first, in ascending numeric order, ahead of the rest in their original sequence. So a document pasted with 2 before b and a is written that way, and the row under the pane shows the order actually used. It is a property of the parse rather than a shuffle by this page, and the only way to catch it in your own data is to be shown the sequence.

Does the output get checked against a DTD, schema or namespace declaration?

No. This page does not validate anything: what it controls is the shape of what it emits — element names, escaping and the one-root rule — and a character XML 1.0 forbids is dropped rather than written. Whether the tree matches a schema your organisation publishes is a question for a validator that knows that schema; undeclared namespace prefixes in particular are treated as ordinary characters to repair rather than emitted as-is, because an undeclared prefix is what makes a document not well-formed.

Will a very large JSON freeze the page?

The parse, the walk and the string assembly all run on the main thread, so a document large enough to strain the tab will strain it; there is no server to hand the work to and no upload that could take it away. The output pane stops painting after twenty thousand characters so a huge result cannot lock the display, while the copy button and the download still carry the complete document. A file that size is better converted by a script next to it.

Is any of my data visible to anyone else?

Nothing is transmitted: the document is read where you typed it and the converted text is assembled in the same tab. The query string can carry the options — the root name, the array style, the prefix, the indentation — which is what lets a link reopen the page on the same settings. Your values are not among the things written there.

Guides that cover this tool

What a converter has to decide before it writes a tag

JSON and XML are not two spellings of one model. An object key has no rules about which characters it may hold; an XML element name does. JSON has null; XML 1.0 does not — a missing value and an empty string both arrive at the same empty element. JSON arrays are ordered lists with no name of their own; XML has no list type, only repeated siblings or a named wrapper. So the useful question about a JSON-to-XML tool is not whether it escapes & (it must) but which of those four gaps it fills by guessing, and whether it tells you. This page fills them with settings you can see and a report that names every place the output had to disagree with the input.

Element names are repaired, and each repair is listed

A JSON key like 2024-total is legal JSON and cannot open an XML name, which has to start on a letter or an underscore. The rule here is: replace a character XML cannot carry inside a name with _, and prefix a name that opens on the wrong kind of character with _ rather than destroying that character. So 2024-total becomes _2024-total, not _024-total; @id becomes _id; my key becomes my_key; and a letter outside ASCII is kept, because XML names are not limited to the Latin alphabet. Every one of those pairs is printed above the output pane. A silent rename is how a field goes missing in a pipeline two steps downstream, and the person debugging it will not think to look at a character that turned into an underscore.

A colon is treated as an ordinary character to repair, not as a namespace separator. Colons are legal in an XML name only when the prefix before them is declared, and a document carrying ns:tag without a matching declaration is not well-formed — so the safe output for an undeclared prefix is a repaired name, and the report says so.

Two array styles, and the one that hides a key

The default is the repeat style: {"parcels": [a, b]} becomes two sibling <parcels> elements, because that is how XML has always said "these belong together" — repetition, with no list construct. The wrap style instead writes one <parcels> containing item elements whose name you set, which is the shape schema validators and RSS-style readers usually expect.

The styles differ in one case people get wrong. An empty array under the repeat style contributes nothing at all — zero items means zero siblings, so the key vanishes from the document; under the wrap style it contributes an empty element and the name survives. This page reports which keys were affected instead of letting the omission look like a typo in your data. A top-level array is the one shape where the two styles agree: it has no key to repeat, so its items always take the item name inside the root.

Keys that want to be attributes

Off by default, because most JSON has no idea it is becoming XML. Switch the prefix to @ or - and any key starting with it is written as an attribute of its parent when its value is a primitive — {"@id": 7, "name": "Ada"} gives <root id="7"><name>Ada</name></root>. An attribute cannot hold structure, so an object or an array under that prefix is demoted back to a child element and named in the report rather than dropped, and two keys that collapse onto one attribute name are resolved the same way, with the second one going to an element.

Escaping is the part that is not a matter of taste

Text content has &, < and > replaced with their references. Attribute values take those three plus the double quote, and also tab, newline and carriage return. The last three are the ones people forget, because a conforming parser normalises whitespace inside an attribute: a line break written literally in a description attribute arrives at the reader as one space, which is a change to your data delivered by software that is technically correct. Emitting desc="line1&#10;line2" with the character reference spelled out is what lets the value survive the trip and come back as a line break again. The count in the output header is the number of substitutions actually made, and the element and attribute counts are read back out of the emitted document rather than tallied during the walk — a statistic that disagrees with the pane is worth less than no statistic.

Characters XML 1.0 forbids outright cannot be escaped into existence; there is no reference form for them. Those are dropped and counted, and the report says how many, which is the only honest option left.

Reading the result before you wire it in

The header under the output pane states the shape of what was written: how many elements, how many attributes, how deep the nesting got, and the size in bytes and lines. Beside it sits the order the keys were emitted in, because an XML reader that displays children in document order will show yours in that one, and it is not always the order you pasted. ECMAScript puts integer-like keys first, in ascending numeric order, ahead of the string keys in their original sequence, so a document pasted as {"b":1, "2":2, "a":3, "1":4} is written 1, 2, b, a. That is a property of the parse rather than a decision by this page, and the only way to know it about your own document is to be shown the order — which is what that row is for.

Two things are worth checking on any output that has to be well-formed somewhere else: the wrapper. XML allows exactly one root element, so the wrapper is on by default and the root name is root until you type another. Switch it off and your object keys become the top-level elements, which only works for exactly one of them — a document that would write two is refused with its count in the message rather than emitted broken. The serializer has the same kind of limit on the other axis: a document nesting past 64 levels is refused, because a stack has a bottom and a converter that runs out of it in the middle of writing produces a truncated file that looks like an output.

What this page is not

It does not write a namespace declaration you did not ask for, a document type definition, a schema, or an XML declaration you switch off — the declaration line is a checkbox because some feeds and some parsers do not want it, and it is on by default because a file that begins with <? and no declaration is a decoding question. It does not validate your output against anything; it makes characters that cannot appear not appear, and names that cannot open a tag not open one. And it does not preserve comments, because JSON has none to preserve — the reverse direction is where that loss shows up, and the page for it says so.

Round-tripping is worth being clear-eyed about. JSON-to-XML-to-JSON is not the same document it started as: an array loses its name under the repeat style, an object and a string that happened to be empty are indistinguishable once both are written as an empty element, and a number keeps its digits but no longer declares itself a number. The converter on this page is designed so that every one of those collapses is counted and visible while you are looking at the output, rather than discovered by a consumer of it.

Nothing leaves the tab

The parse, the walk and the string assembly all happen in this page. There is no upload step, no queue, and no server that could keep a payload after the conversion. That is also why the address bar can carry your settings and never carries your document: ?arrayStyle=wrap&itemName=record is shareable, your JSON is not.

Files never leave your device; calculations and transformations happen locally.

Last reviewed: 2026-11-03