Convert JSON to XML for SOAP endpoints, enterprise systems and config formats that never moved to JSON. Keys become tags, arrays become repeated elements, and keys that start with an underscore become attributes. The converter does not add a root element, so wrap the JSON in one top-level key such as catalog to get a document that parses.
{
"root": {
"person": [
{
"id": "1",
"name": "John Doe",
"email": "john@example.com",
"active": "true"
},
{
"id": "2",
"name": "Jane Smith",
"email": "jane@example.com",
"active": "false"
}
]
}
} <root>
<person>
<id>1</id>
<name>John Doe</name>
<email>john@example.com</email>
<active>true</active>
</person>
<person>
<id>2</id>
<name>Jane Smith</name>
<email>jane@example.com</email>
<active>false</active>
</person>
</root> Any service described by a WSDL expects an XML envelope, whatever format the rest of your stack uses.
Tax filing formats, banking messages such as ISO 20022 and older B2B integrations are specified in XML.
XSLT, XPath and XSD validation work on XML, and some pipelines have no JSON step to replace them.
const raw = '{"user":{"id":1,"name":"Ada & Co","roles":["admin","dev"],"active":true}}';
const esc = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
function toXml(value, name, depth = 0) {
const pad = " ".repeat(depth);
if (Array.isArray(value)) return value.map((v) => toXml(v, name, depth)).join("");
if (value !== null && typeof value === "object") {
const inner = Object.entries(value).map(([k, v]) => toXml(v, k, depth + 1)).join("");
return pad + "<" + name + ">\n" + inner + pad + "</" + name + ">\n";
}
return pad + "<" + name + ">" + esc(value ?? "") + "</" + name + ">\n";
}
const data = JSON.parse(raw);
console.log('<?xml version="1.0" encoding="UTF-8"?>\n' + toXml(data, "root")); Good enough for element-only documents. Once you need attributes, namespaces or CDATA, switch to xmlbuilder2 or the XMLBuilder class in fast-xml-parser.
<?php
$raw = '{"user":{"id":1,"name":"Ada & Co","roles":["admin","dev"],"active":true}}';
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
function addNode(SimpleXMLElement $parent, string $name, mixed $value): void {
if (is_array($value) && array_is_list($value)) {
foreach ($value as $item) addNode($parent, $name, $item);
} elseif (is_array($value)) {
$child = $parent->addChild($name);
foreach ($value as $k => $v) addNode($child, $k, $v);
} else {
// Assign through [0] so & < > are escaped; addChild($name, $value) drops "&"
$child = $parent->addChild($name);
$child[0] = is_bool($value) ? var_export($value, true) : (string) $value;
}
}
$xml = new SimpleXMLElement('<root/>');
foreach ($data as $k => $v) addNode($xml, $k, $v);
$dom = dom_import_simplexml($xml)->ownerDocument;
$dom->formatOutput = true;
echo $dom->saveXML(); array_is_list() needs PHP 8.1. Assigning through $child[0] escapes & and < correctly; addChild($name, $value) does not escape &.
import json
import xml.etree.ElementTree as ET
raw = '{"user":{"id":1,"name":"Ada & Co","roles":["admin","dev"],"active":true}}'
def add(parent, name, value):
if isinstance(value, list):
for item in value:
add(parent, name, item)
elif isinstance(value, dict):
node = ET.SubElement(parent, name)
for k, v in value.items():
add(node, k, v)
else:
node = ET.SubElement(parent, name)
node.text = "" if value is None else json.dumps(value) if isinstance(value, bool) else str(value)
root = ET.Element("root")
for k, v in json.loads(raw).items():
add(root, k, v)
ET.indent(root, space=" ") # Python 3.9+
print(ET.tostring(root, encoding="unicode", xml_declaration=True)) ET.indent() needs Python 3.9. For keys that should become attributes, pass attrib={...} to ET.SubElement.
There is no standard mapping from JSON to XML, only conventions. This tool runs on x2js: keys become elements, arrays become repeated siblings, scalars become text nodes, and a leading underscore marks an attribute. Badgerfish uses an @ prefix instead, and other libraries have their own rules. Find out what the receiving system expects before you commit to one.
XML cares about element order, and JSON objects officially do not. If the consumer validates against an XSD that uses xs:sequence, put the keys in the JSON in the order the schema wants before you convert.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Yes, through a naming convention. A key that starts with an underscore becomes an attribute of its parent element, so "_id": 5 turns into id='5'. Every other key becomes a child element.
Each item in an array becomes a repeated element named after the array's key. An array under "items" produces several <items> elements, with no wrapper element around them.
They are copied as they are. A key such as "first name" or "2024" produces a tag that no XML parser accepts, so rename those keys in the JSON before converting.
No. The converter writes one element per top-level key, so JSON with two top-level keys, or a top-level array, produces XML without a single root, and that does not parse. Wrap the data in one key, for example {"catalog": ...}, to get one root element.
Yes. &, <, >, double quotes and apostrophes are escaped in element text and attribute values, so the content cannot break the markup.
XML to JSON converts in the other direction. For configuration rather than messages, JSON to YAML is usually the better target.