All posts

XML to JSON: Converting Legacy Data for Modern APIs

July 26, 2026 · DevTools

xml
json
conversion
api
developer-tools

The third-party integration you depend on still ships its responses as XML. Your service consumes JSON. Something in the middle has to translate — and the moment you try to flatten <order><id>42</id></order> into a JSON object, the question that has tripped up developers for fifteen years reappears: what do we do with the attributes?

You can try every example in this guide with the XML to JSON Converter. It runs entirely in your browser, so even customer records and integration payloads never leave your machine.

Why JSON won — and why XML still won't go away

XML was the lingua franca of web services in the SOAP era: verbose, schema-strict, and tolerant of namespaces. JSON won the next generation because it mapped cleanly to JavaScript objects, was easier to read, and produced smaller payloads for the kind of record-shaped data most web APIs actually exchange. REST APIs, GraphQL endpoints, modern SaaS products — nearly all default to JSON now.

But XML is still everywhere: RSS feeds, SVG documents, SOAP endpoints, Android manifests, Maven artifacts, Office's OOXML format, SAML assertions, and a long tail of enterprise integrations that pre-date the JSON era. Converting between the two isn't going away; it's a recurring chore you can make safer.

The mapping that always works

XML is a tree of elements. JSON is a tree of objects and arrays. The straightforward conversion turns every element into a JSON object whose keys are the children:

<order>
  <id>42</id>
  <customer>Ada</customer>
  <total>199.95</total>
</order>

becomes

{
  "order": {
    "id": "42",
    "customer": "Ada",
    "total": "199.95"
  }
}

Children that appear more than once become arrays automatically:

<orders>
  <item><sku>A1</sku></item>
  <item><sku>B2</sku></item>
</orders>
{
  "orders": {
    "item": [
      { "sku": "A1" },
      { "sku": "B2" }
    ]
  }
}

This much is uncontroversial — the XML to JSON Converter handles it the same way.

Where it gets contested: attributes

This is the source of most arguments about XML-to-JSON conversion. XML lets elements carry both text content and attributes:

<product id="42" currency="USD">Widget</product>

There are at least three reasonable JSON shapes, and you have to pick one based on what your downstream code expects:

ShapeOutputWhen to use it
Attributes prefixed with @_{"product": {"@_id": "42", "@_currency": "USD", "#text": "Widget"}}Most general; preserves everything; matches the popular xml2js convention
Attributes merged into the object{"product": {"id": "42", "currency": "USD", "text": "Widget"}}When the consumer doesn't care about attribute-vs-element distinction
Attributes nested in ${"product": {"$": {"id": "42", "currency": "USD"}, "_": "Widget"}}jQuery's old parseXML convention; rarely useful today

The @_ prefix and #text key together are the convention most JavaScript and Python libraries default to — xml2js in Node, xmltodict in Python, xml.etree plus a tiny adapter in others. Use this shape when in doubt: it's unambiguous, you can always strip the prefix later, and it round-trips back to XML without losing information.

The converter exposes both options, so you can flip the prefix or merge attributes entirely depending on what your downstream code expects.

Whitespace, numbers, and the type-loss trap

XML doesn't distinguish strings from numbers — everything between tags is text. When you convert to JSON, you have to decide:

<price>19.99</price>
<count>3</count>
<active>true</active>

A naive conversion gives you three strings:

{ "price": "19.99", "count": "3", "active": "true" }

A smarter parser notices the booleans and numbers and produces real types:

{ "price": 19.99, "count": 3, "active": true }

Both are defensible — but pick one and apply it consistently, because consumers usually do if (data.count > 0) and a string "3" will silently pass that test while breaking every arithmetic comparison.

Whitespace is the other silent gotcha. Element text often contains indentation and newlines from pretty-printing:

<note>
  Reopened, see thread
</note>

If you don't trim, your JSON value is "\n Reopened, see thread\n". The converter's "trim values" toggle strips that for you — usually a good idea.

Namespaces: the part everyone hates

Real-world XML uses namespaces, and they make JSON ugly:

<ns:order xmlns:ns="http://example.com/api">
  <ns:id>42</ns:id>
</ns:order>

The clean options are:

  • Drop the prefix and keep the local name: {"order": {"id": "42"}} — easiest to consume
  • Keep the prefix: {"ns:order": {"ns:id": "42"}} — preserves the wire format
  • Group under a xmlns key: {"order": {"id": "42"}, "_xmlns": {"ns": "http://example.com/api"}} — best of both worlds

There's no universally correct answer. Most teams drop the prefix and move on, because their consumers don't care about namespace disambiguation.

CDATA, mixed content, and the edge cases that wreck parsers

A few XML features don't map to JSON at all, and any converter is making opinionated choices:

  • CDATA sections (<![CDATA[ <stuff> ]]>) — the raw text inside is treated as plain element text
  • Mixed content (Hello <b>world</b>) — has no JSON equivalent; the converter usually picks the dominant text or the inline elements
  • Processing instructions (<?xml version="1.0"?>) — dropped entirely
  • Comments (<!-- ... -->) — dropped entirely by default; keep them if you need round-trip fidelity
  • XML declarations and DOCTYPEs — dropped; they're metadata, not data

If round-tripping matters (you need to be able to go JSON → XML → same XML), pick a converter that preserves comments and CDATA. If round-tripping doesn't matter (most one-way API integrations), drop them and accept the loss.

Validating the result

Conversion is only half the job. After you've turned XML into JSON, check the shape:

  1. Paste into the JSON Formatter — it validates syntax and lets you scan the structure visually.
  2. Confirm array vs object — if the input has one <item> you get an object; if it has many, you get an array. Decide which your consumer expects and coerce if needed (wrap the single one in [result]).
  3. Run a JSON Schema check — if your downstream has a schema, validate the output against it before going to production.
  4. Round-trip one record — convert back to XML and diff against the original. Any difference is data loss.

The round-trip is the cheapest test that catches the dangerous bugs: silently dropped attributes, lossy namespaces, stripped CDATA. If you can't round-trip a sample, you can't trust the converter.

A complete before-and-after

Realistic input — attributes, repeated children, mixed types, a CDATA section:

<order id="42" currency="USD">
  <customer email="ada@example.com">Ada Lovelace</customer>
  <items>
    <item sku="A1"><name>Widget</name><qty>2</qty></item>
    <item sku="B2"><name>Sprocket</name><qty>1</qty></item>
  </items>
  <note><![CDATA[Handle with care — fragile]]></note>
</order>

Converted with attributes prefixed @_, trimming on, type coercion on:

{
  "order": {
    "@_id": "42",
    "@_currency": "USD",
    "customer": {
      "@_email": "ada@example.com",
      "#text": "Ada Lovelace"
    },
    "items": {
      "item": [
        { "sku": "A1", "name": "Widget", "qty": 2 },
        { "sku": "B2", "name": "Sprocket", "qty": 1 }
      ]
    },
    "note": "Handle with care — fragile"
  }
}

Notice what survived: the order id and currency (as attributes), the customer's email (as attribute), the two items (as an array), the CDATA contents (as plain text), and the numbers (as numbers). That's the difference between a parser that knows XML and a regex that fights it.

Common mistakes

  • Assuming null for empty elements<note></note> usually becomes "", not null. Decide whether you want to coerce empty strings to null in your application layer.
  • Ignoring character encoding — XML files saved as Latin-1 or UTF-16 will look corrupt. Re-save as UTF-8 (every editor can do this) before converting.
  • Trusting the first element name as a "type" — many XML responses have a single root element like <response> wrapping the real data. Strip the root or accept the extra nesting.
  • Merging attributes with elements naively<product id="A1"/> and <product><id>A1</id></product> are semantically different inputs. A converter that merges them throws away information.

Everything stays in your browser

XML payloads often contain customer records, integration tokens, or proprietary schemas. The XML to JSON Converter parses everything client-side — nothing is uploaded, no telemetry, no server log of your data. For most teams that's the difference between a tool you can paste production XML into and one you can't.

Next steps