Why Convert JSON to XML?
JSON has been the de facto data format for REST APIs since roughly 2012, but XML never went away. Enterprise systems, SOAP web services, RSS/Atom feeds, Office Open XML documents (.docx, .xlsx), Android layouts, SEPA banking messages, HL7 healthcare payloads, and countless XSLT transformation pipelines all speak XML natively. Any team building modern services on top of legacy infrastructure eventually needs to convert JSON output back into XML for a downstream consumer.
The conversion is not free. Unlike JSON, XML distinguishes between attributes (metadata on an element) and elements (nested children), supports namespaces for schema disambiguation, requires a single root element, and has semantically-meaningful CDATA sections for embedded markup. JSON has none of these. Converting requires picking conventions — and different XML consumers expect different conventions.
The Standard Conversion Conventions
Two conventions dominate: the xmltodict / BadgerFish style and the elements-only style. The xmltodict convention (used by the Python `xmltodict` library and JavaScript `xml-js`) uses key prefixes to distinguish attributes from elements. A JSON key starting with @ becomes an attribute, and the special key #text becomes the element body. So {"a": {"@id": "1", "#text": "hello"}} becomes <a id="1">hello</a>.
The elements-only convention drops attributes entirely and represents every JSON key as a child element. This is simpler but loses information when round-tripping XML → JSON → XML, because the original attributes become nested elements that no longer round-trip cleanly. Use elements-only when the target XML consumer does not care about attributes; use xmltodict-style when preserving attribute semantics matters.
JSON to XML in JavaScript / Node.js
// Using the xml-js library
const convert = require('xml-js');
const jsonStr = JSON.stringify({
bookstore: {
book: [
{ _attributes: { category: 'fiction' }, title: '1984', author: 'Orwell' },
{ _attributes: { category: 'tech' }, title: 'Clean Code', author: 'Martin' }
]
}
});
const xml = convert.json2xml(jsonStr, { compact: true, spaces: 2 });
// <bookstore>
// <book category="fiction"><title>1984</title><author>Orwell</author></book>
// <book category="tech"><title>Clean Code</title><author>Martin</author></book>
// </bookstore>JSON to XML in Python
import json, dicttoxml
data = json.loads('{"user": {"name": "Alice", "age": 30, "tags": ["admin", "dev"]}}')
xml_bytes = dicttoxml.dicttoxml(data, custom_root='root', attr_type=False)
print(xml_bytes.decode())
# Or with xmltodict for round-trip-friendly output:
import xmltodict
data = {'root': {'user': {'@id': '1', '#text': 'Alice'}}}
print(xmltodict.unparse(data, pretty=True))
# <root><user id="1">Alice</user></root>Handling Arrays
XML has no array type — this is the single trickiest part of the conversion. The universal convention is to emit each array item as a sibling element with the same tag name. {"items": [1, 2, 3]} becomes <items>1</items><items>2</items><items>3</items>. Some conventions wrap the array under a parent element: <items><item>1</item><item>2</item><item>3</item></items>. Neither is wrong, but SOAP schemas and XSDs usually enforce one specific shape — check your target schema before choosing.
Empty arrays produce nothing (no elements emitted). Single-item arrays produce exactly one element — which means the JSON→XML→JSON round-trip loses arrayness for single-item lists. This is the classic "SOAP breaks when the list has one element" bug and the reason many XML APIs mandate always-present wrapper elements.
Escaping and Special Characters
XML requires five characters to be escaped inside element text and attribute values: < becomes <, > becomes >, & becomes &, ' becomes ', and " becomes ". Skipping this step produces XML that fails validation and breaks downstream parsers. Our converter handles all five automatically. For binary or heavily-marked-up content, wrap in a <![CDATA[...]]> section instead of escaping.
Common Pitfalls
1. Root element required. XML documents must have exactly one root element. JSON allows a top-level array — you must wrap it: {"items": [...]} or supply an explicit root name during conversion.
2. Invalid element names. XML element names cannot start with a digit, contain spaces, or start with the letters "xml" (case-insensitive). If your JSON keys break these rules, either sanitize (replace with underscores) or wrap the value inside a legal element.
3. Null values. JSON null has no XML equivalent. Options: emit an empty element (<key/>), emit xsi:nil="true" (requires namespace declaration), or omit the key entirely. Pick one and be consistent.
Related JSON Tools
- JSON Formatter (Parent Tool) — the underlying formatter used by every variant
- JSON to YAML Converter — for modern config file targets
- Format JSON Online — beautify before converting
- Validate JSON Online — ensure input is valid before conversion
- JSON Schema Validator — validate structure against a schema