Converters

JSON to XML

Convert JSON data to XML format.

Input JSON
XML result

JSON to XML Converter

Converts JSON data to XML format. Useful for system integration where different formats are required.

Features

  • Automatic structure conversion
  • Array handling with item tags
  • Pretty print output
  • Custom root element name

Use Cases

  • Legacy system integration
  • SOAP web service calls
  • Data format migration

Converts a JSON document into XML: every object key becomes an element name, keys prefixed with @_ become attributes, #text becomes the element text, and the result is wrapped in a root element you name. Suited to producing one-way XML payloads for legacy or SOAP-style endpoints that cannot accept JSON.

Where attributes and text come from

The builder runs with attributeNamePrefix: "@_", textNodeName: "#text" and ignoreAttributes: false — the same spelling fast-xml-parser emits when it parses XML. An object therefore gains attributes only if your keys already carry that prefix: {"book":{"@_id":1,"#text":"Dune"}} becomes <book id="1">Dune</book>, while a plain id key stays a child element, which is the usual reason an expected attribute comes out as a nested tag. The wrapper root is plain string concatenation, so attributes and text sitting on the top-level object are discarded: {"@_id":"7","#text":"x"} yields <root>x</root> with no id anywhere. When a mapped object also holds ordinary keys, its text is placed before those children rather than around them.

Arrays become repeated siblings

XML has no array type, so the builder repeats the element name: {"features":["a","b"]} emits two sibling <features> elements and no container element around them. Length is then carried only by repetition count, so a one-element array is indistinguishable from a scalar — ["admin"] and "admin" both produce a single <roles>admin</roles>, and the receiving system cannot tell which was sent. Arrays nested inside arrays fall back to positional names: [[1,2]] becomes <m><0>1</0><1>2</1></m>, and because a tag name may not begin with a digit, that fragment is rejected by any conforming parser. An empty array emits nothing at all, wiping the field from the output, and a top-level JSON array expands the same way into <0>, <1>, <2> siblings.

Where the output stops being well formed

Keys are copied into tag names with no sanitisation: my key contains a space and 1st starts with a digit, either of which breaks well-formedness, while a:b reads as a namespace prefix and fails because no xmlns declaration is ever written. Attribute values are no safer — a boolean produces <p x></p>, a bare attribute name with neither equals sign nor value, which XML forbids. Text escapes &, < and > and attributes additionally escape quotes, but nothing emits an XML declaration, a CDATA section or a namespace. Numbers are stringified by JavaScript, so {"id":9007199254740993} prints 9007199254740992 and 1e21 prints 1e+21, legal for xsd:double yet rejected as xsd:decimal. Finally, format: true with a two-space indent scatters whitespace-only text nodes through the tree, so compare parsed values after trimming, never the raw strings.

Worked example

An order object with one attribute and a two-item array

Input
{"order":{"@_id":"A-1001","items":["cable","hub"],"total":19.9,"paid":true}}
Output
<root><order id="A-1001">
  <items>cable</items>
  <items>hub</items>
  <total>19.9</total>
  <paid>true</paid>
</order>
</root>

The @_ prefix turned id into an attribute, and the two array values became two sibling items elements rather than a list. 19.9 and true are text, not numbers or booleans: XML carries no type unless an xsi:type is added. The wrapper is concatenated after the fact, so <root> shares its line with the first child and the builder trailing newline sits just before </root>.

Limits and pitfalls

  • No XML declaration, encoding attribute, namespace declaration or CDATA section is generated; consumers that demand an <?xml version="1.0" encoding="UTF-8"?> prolog or a namespace-qualified schema need those added by hand after copying.
  • Key names and the root name are inserted verbatim and never validated: my key, 1st or a root of my root all produce output that conforming parsers reject, and a whitespace-only root silently falls back to root.
  • Numbers pass through JavaScript doubles: integers beyond 2^53 lose their last digits, and exponential forms such as 1e+21 are printed literally, so integer IDs should be sent as JSON strings.
  • An empty array emits no element, so the field simply disappears; null renders as <a/> and an empty string as <b></b>, which parse to identical empty content and cannot be told apart afterwards.
  • Conversion runs synchronously on every keystroke with no input size cap in the component, so pasting a multi-megabyte JSON string stalls typing; convert large documents in a script instead.

Related tools

Six receivers, six answers to "just convert it" — and which ones this page cannot serve

The receiver you are feedingThis page (JSON → XML)What to pick instead
Their XSD declares the identifier as an attribute on the elementAttributes exist only if you edit the JSON before pasting: keys are copied verbatim, so a plain id key always becomes a child element, and only the @_ prefix this builder is configured with puts the value inside the start tag as id="A-1001".Read the schema first, then reshape the JSON: on a 40-record batch, moving one field from child element to attribute saved 320 bytes and 40 lines. Do not reach for the prefix on a key whose value is an array — that emits a literal <@_x> element, and @ is not a legal name character. If the schema is fixed and you cannot touch the source JSON, generate from a template instead.
The same export gets converted again next weekThere is no file picker, no URL field and no download button — the output pane carries a single Copy action, and the root name box is the only setting in the toolbar. That box starts at root every time the page loads, so a recurring job means re-pasting and re-typing.Ten lines around the same fast-xml-parser XMLBuilder in Node, with the root name and indentBy written into the script, makes the step repeatable and diffable. Switch as soon as the conversion has a schedule rather than a one-off audience.
The XML has to be compact, or indented at something other than two spacesThe builder runs with format: true and indentBy: " " as fixed options — there is no indent selector and no compact mode, so every copy is pretty-printed even when the consumer wants one long line.Paste the result into the site XML formatter, which rebuilds it at 1, 2 or 4 spaces and offers a compressed pane; in a build step, xmllint --noblanks or the same builder with format: false does it. On the 40-record sample that is 4,493 bytes down to 4,053.
You have to know the output is well formed before it leaves the browserThe page never parses what it produces: a key containing a space, an empty boolean attribute such as <p x>, or a nested array turning into <0> all pass straight into the copy button as text that no conforming parser accepts.Round-trip the file through a parser — xmllint --noout out.xml, or the site XML formatter, which parses whatever you paste into it and reports the failure in red instead of printing something plausible. Do this whenever the file is heading to a system whose logs you cannot read.
The endpoint validates types, so booleans and decimals must stay typedEvery leaf leaves as text: true, 19.9 and "19.9" are indistinguishable in the output, and prefixing a key with @_ does not add a type either. Nothing writes xsi:type or an xmlns:xsi to bind it.Let a template emit the typed form — xsi:type="xsd:boolean" alongside its namespace declaration — or send the value as the string the schema expects and let the consumer coerce. Keep this page for payloads that only need to be read.
The JSON you are pasting does not parse, and the batch is too big to bisect by handThe failure replaces the entire output pane with the browser's own JSON.parse wording — an offset into the pasted string, plus a line and column on newer engines — while the input textarea stays unmarked: no caret jump, no highlighted character, so a forty-record paste still means scrolling and counting.jq . shipments.json prints the offending line and column and can be re-run after every edit without re-pasting, and the site JSON formatter shows the same message beside a copy you can scroll. Repair the syntax there, then come back once the input is known good.

One concrete scenario

Attribute or child element: the 320 bytes behind a 40-record shipment batch

A 40-record shipment export — [{"@_code":"SHP-0001","weight":12.5,"carrier":"DHL","eta":"2026-10-24"}, …] — is 2,854 bytes once minified. Pasted here with the identifier as an @_code attribute, the output is 4,493 bytes over 201 lines, and every extra record adds exactly 112 bytes and 5 lines. Turn that one field back into a plain code key and the same batch reaches 4,813 bytes over 241 lines: 320 bytes and 40 lines more, because a child element pays for its own start tag, end tag and indented line. So the prefix follows the receiving schema, never the byte count — and a key whose value is an array must stay unprefixed. If 4,493 bytes is still too much, strip the indent with the XML formatter, not the fields.

Frequently Asked Questions

What does a JSON array become in XML?
XML has no array concept, so the tool maps elements to repeated child elements (……). Round-trips are not guaranteed lossless: if key names happen to look like array wrappers, the structure can shift after a round-trip — worth checking when designing interfaces.
What if a JSON key is not a legal XML tag name?
XML tag names cannot start with a digit or contain spaces. This builder writes keys verbatim and does not substitute placeholder names, so such a key produces output that looks complete but is rejected by any conforming parser. Fix the keys at the API layer instead of relying on conversion.
How do I add namespaces or a root element?
The tool lets you set the root element name. Namespace-specific targets (SOAP, enterprise XSDs) usually just need the namespace declarations added after conversion — declarations do not affect element structure.

Content reviewed: