I was six months into a greenfield project when the email arrived: "We need to integrate with the mainframe inventory system."
Our entire stack was modern—React frontend, Node.js APIs, MongoDB, all speaking JSON. The mainframe? It spoke a dialect of SOAP/XML that looked like it was written in 2003 and never updated. The integration spec was 47 pages of XML schemas, namespaces, and element definitions.
The task was simple on paper: take a JSON order from our API, convert it to XML, send it to the mainframe, get XML back, convert to JSON. Simple, right?
I spent the next three weeks learning that JSON-to-XML conversion is never simple.
The Fundamental Schema Mismatch
Here's the core problem: JSON and XML have fundamentally different data models.
JSON is object-oriented. Everything is either a value, an array, an object, or a primitive. There are no attributes vs elements—just key-value pairs.
XML is document-oriented. It distinguishes between attributes (<item id="123">) and child elements (<item><id>123</id></item>). It has namespaces, processing instructions, and mixed content (text + child elements in the same node).
// Simple JSON object
{
"product": {
"id": "SKU-001",
"name": "Wireless Mouse",
"price": 29.99,
"inStock": true,
"categories": ["electronics", "accessories"],
"supplier": {
"name": "TechDistributor",
"tier": "premium"
}
}
}
This single JSON object could map to XML in at least three different ways, depending on how the legacy system expects it.
Mapping Strategy: Attributes vs Elements
The biggest decision you'll make in JSON-to-XML conversion is what goes in attributes vs what goes in elements.
Option A: Everything as elements (safe, verbose)
<product>
<id>SKU-001</id>
<name>Wireless Mouse</name>
<price>29.99</price>
<inStock>true</inStock>
<categories>
<category>electronics</category>
<category>accessories</category>
</categories>
<supplier>
<name>TechDistributor</name>
<tier>premium</tier>
</supplier>
</product>
This is the safest approach. Everything becomes an element. Arrays get a wrapper element (I used <categories>) with repeated children. No ambiguity, but it's verbose.
Option B: Simple values as attributes (compact, risky)
<product id="SKU-001" name="Wireless Mouse" price="29.99" inStock="true">
<categories>
<category>electronics</category>
<category>accessories</category>
</categories>
<supplier name="TechDistributor" tier="premium"/>
</product>
This is what the legacy system expected in my case. Primitive values become attributes, objects and arrays stay as elements. But you need a set of rules to decide what's "simple" vs "complex."
def json_to_xml_with_rules(key, value, simple_types={str, int, float, bool}):
"""Convert JSON to XML with attribute/element rules."""
if isinstance(value, dict):
elem = ET.Element(key)
for k, v in value.items():
if type(v) in simple_types:
elem.set(k, str(v)) # Simple → attribute
else:
elem.append(json_to_xml_with_rules(k, v)) # Complex → child
return elem
elif isinstance(value, list):
parent = ET.Element(key)
for item in value:
child_key = key.rstrip('s') # "categories" → "category"
parent.append(json_to_xml_with_rules(child_key, item))
return parent
else:
elem = ET.Element(key)
elem.text = str(value)
return elem
This worked... until it didn't. A boolean false became the string "False", and the mainframe rejected it because it expected lowercase "false".
Handling XML Namespaces Without Tears
Namespaces are where most of my bugs lived. The legacy system used a namespace like http://inventory.legacycorp.com/schema/2020 on every single element.
<inv:order xmlns:inv="http://inventory.legacycorp.com/schema/2020" xmlns:addr="http://address/v2">
<inv:customer>
<inv:name>Acme Corp</inv:name>
</inv:customer>
<addr:shipping>
<addr:city>New York</addr:city>
</addr:shipping>
</inv:order>
My first attempt ignored namespaces entirely. The mainframe responded with a cryptic error: "Namespace mismatch at element 'order'."
The fix was to build a mapping configuration that the conversion script followed:
NAMESPACE_MAP = {
"order": "http://inventory.legacycorp.com/schema/2020",
"customer": "http://inventory.legacycorp.com/schema/2020",
"shipping": "http://address/v2",
}
def apply_namespace(elem, ns_map):
"""Apply namespaces to XML elements based on a mapping."""
tag = elem.tag.split('}')[-1] # Strip existing ns if any
if tag in ns_map:
ns = ns_map[tag]
elem.tag = f"{{{ns}}}{tag}"
for child in elem:
apply_namespace(child, ns_map)
I stored the namespace map in a config file alongside the JSON schema. When the legacy team added a new namespace (which happened twice in the first month), I just updated the config, no code changes needed.
Dealing With XML Mixed Content
Here's something I wish I'd known before week two: XML allows mixed content—text and child elements in the same parent. JSON doesn't have a direct equivalent.
<description>
This product is
<highlight>wireless</highlight>
and supports
<feature>Bluetooth 5.0</feature>
connectivity.
</description>
How do you represent this in JSON? You can't, really. The cleanest approach I found was a special structure:
{
"description": {
"_text": "This product is {0} and supports {1} connectivity.",
"_children": [
{"highlight": "wireless"},
{"feature": "Bluetooth 5.0"}
]
}
}
It's ugly, but it's explicit. The converter replaces {0}, {1} placeholders with the serialized child elements.
The Conversion Pipeline That Finally Worked
After three weeks of trial and error, I settled on a pipeline that handled 95% of our cases:
- Validate the source JSON (no malformed input)
- Flatten nested objects according to a mapping config
- Apply attribute/element rules (simple = attr, complex = elem)
- Inject namespaces from a config file
- Serialize with proper encoding (XML declaration, UTF-8)
- Validate the XML output against the XSD schema
The remaining 5%—mixed content, CDATA sections, and processing instructions—I handled with manual override templates.
Using a tool that handles both validation and conversion in one step would have saved me days. The JSON Formatter supports JSON-to-XML conversion right in the browser, with proper attribute handling and namespace support. It validates your input before converting, which catches a lot of the edge cases I hit manually.
FAQ
Q: Can I convert JSON to XML and back without data loss?
A: Not perfectly. XML has concepts that don't exist in JSON (attributes, namespaces, processing instructions, mixed content). When you convert XML→JSON→XML, you'll lose the distinction between attributes and elements, and namespace prefixes get flattened.
Q: How do I handle JSON arrays in XML?
A: The most common approach is a wrapper element named after the array, with repeated child elements. For example, ["a", "b"] becomes <items><item>a</item><item>b</item></items>. Some schemas prefer comma-separated values in a single element.
Q: Should XML attributes be in the JSON or added during conversion?
A: Keep them out of the JSON if possible. Your conversion layer should handle attribute assignment based on rules (simple types = attributes, complex = elements). This keeps your JSON schema clean and your XML mapping logic centralized.
Q: What's the best way to test JSON-to-XML conversion?
A: Validate the output against the target XSD schema. If the legacy system provides an XSD, run the generated XML through a schema validator before sending it. This catches about 80% of integration errors before they reach production.
Q: How do CDATA sections work in JSON-to-XML?
A: CDATA is a XML-specific concept. If your legacy system expects CDATA for certain fields, add a marker in your JSON (like {"field": {"_cdata": "content here"}}) and have your converter handle it specially.
Q: What about XML prolog and encoding declarations?
A: Most converters add <?xml version="1.0" encoding="UTF-8"?> automatically. Check if your legacy system requires a specific encoding—I've seen mainframes that choke on UTF-8 and need ISO-8859-1.
Q: Can I do JSON-to-XML conversion entirely in the browser?
A: Yes, and it's often preferred since you're not sending potentially sensitive data to a server. Tools like the JSON Formatter run 100% in the browser, converting JSON to XML (and back) locally with configurable attribute handling.
Q: What's the hardest JSON structure to convert to XML?
A: Arrays of mixed-type objects, like [{"type": "a", "val": 1}, {"type": "b", "label": "hello"}]. XML expects homogeneous child elements, so you need wrapper elements or type-specific element names to represent this cleanly.
If you're staring down a legacy integration spec right now, the first step is getting your JSON clean and validated. The JSON Formatter can validate, format, and convert your JSON to XML in one go—no server uploads, no data leaving your browser. It saved me hours of manual conversion debugging.