XML to JSON Converter

Paste a fragment or drop the whole file. Out comes one JSON object per record, with attributes and nested elements folded into dotted keys and the same key set on every object. Your document never leaves the tab.

Need to rename keys or drop fields on the way? Open the app

Records, not documents

There are two reasons to convert XML into JSON, and they want different output. One is to mirror a document faithfully, tree and all, which usually means writing a schema-aware mapping by hand. The other is far more common: a repeating structure needs to become a list of objects that code can loop over. This page does the second one, and says so up front so you can leave now if you needed the first.

  • A legacy endpoint in a modern app. Insurance, logistics, banking and telecom services still answer in XML. The React or Node side of the wall wants objects, not a DOM walk.
  • Seeding a document store. MongoDB, Firestore, OpenSearch and DynamoDB all ingest JSON. Handing them an XML export requires this step first.
  • Test fixtures from a real response. Capture one live payload, convert it, and you have a fixture your mocks can return without anybody hand typing forty fields.
  • Feeds worth tracking over time. Sitemaps, RSS, product feeds. As JSON they diff cleanly in version control and drop into a script in one line.
  • Getting XML into a notebook. A list of flat objects loads straight into pandas or Polars, where an unflattened tree would need normalising first.

Most converters for this pair are paste boxes with a character limit. This one takes a file too, up to 100 MB, and reads it locally, which matters when the payload you captured has customer names in it.

Worked example: a catalogue with an optional field

Two products. Attributes on the record element, an attribute sitting on a price, a shipping block underneath, and a clearance date that only the second one carries.

<catalog>
  <product sku="PL-77" active="true">
    <title>Planter, terracotta</title>
    <price currency="USD">18.00</price>
    <shipping>
      <weight>2.1</weight>
      <origin>MX</origin>
    </shipping>
  </product>
  <product sku="PL-78" active="false">
    <title>Planter, glazed</title>
    <price currency="USD">24.00</price>
    <shipping>
      <weight>2.4</weight>
      <origin>MX</origin>
    </shipping>
    <clearance>2026-03-01</clearance>
  </product>
</catalog>

With the widget on its defaults, a pretty printed array, that comes back as:

[
  {
    "sku": "PL-77",
    "active": true,
    "title": "Planter, terracotta",
    "price.currency": "USD",
    "price": "18.00",
    "shipping.weight": 2.1,
    "shipping.origin": "MX",
    "clearance": null
  },
  {
    "sku": "PL-78",
    "active": false,
    "title": "Planter, glazed",
    "price.currency": "USD",
    "price": "24.00",
    "shipping.weight": 2.4,
    "shipping.origin": "MX",
    "clearance": "2026-03-01"
  }
]

Five things happened there worth naming. product repeats under the root, so it became the record. The attributes sku and active arrived as plain keys with no @ in front of them, and active is a real boolean rather than the string "true". The shipping block turned into shipping.weight and shipping.origin. The price element carried an attribute and a value at once, so it split into price.currency and price, attribute first.

The fifth is the one people notice later. Product PL-77 has no clearance element anywhere in the source, and its object still has a clearance key, set to null.

One key set, every object

Optional elements are the normal state of XML, not an edge case. A converter that simply mirrors each record produces objects of different shapes, and everything downstream then behaves badly in a way that takes a while to trace. A schema inferred from the first record is wrong for the rest of the file. A CSV writer given that array emits ragged rows. TypeScript types generated from a sample declare fields as required that are missing three hundred records later.

So the keys are collected across the whole document first, then every object is written with all of them. A record that never mentioned a field gets it as null. The order is first seen wins, which is why clearance appears last in both objects above rather than in the middle of the second one.

The one thing this costs you is the ability to tell "the element was absent" apart from "the element was there and empty". Both are null. If that distinction carries meaning in your source, keep the XML around, because the JSON no longer holds the answer.

Four shapes from two toggles

Formatting and Structure sit under the result. Switching Structure to JSON Lines gives you the same records with no wrapping array and one compact object per line:

{"sku":"PL-77","active":true,"title":"Planter, terracotta","price.currency":"USD","price":"18.00","shipping.weight":2.1,"shipping.origin":"MX","clearance":null}
{"sku":"PL-78","active":false,"title":"Planter, glazed","price.currency":"USD","price":"24.00","shipping.weight":2.4,"shipping.origin":"MX","clearance":"2026-03-01"}
  • Take the array when one JSON.parse has to swallow the whole thing: a request body, a fixture you import, a config file, anything you will paste into a viewer.
  • Take JSON Lines when something reads a record at a time. Warehouse loaders, log shippers and model training formats all expect exactly this, and a file in this shape can be read without parsing the rest of it.
  • Pretty or minified applies to the array. Pretty indents by two spaces. Minified is one line, which is what you want for a request body or a fixture that should not produce diff noise. Lines are always compact, since indenting them would break the one record per line contract.
  • The extension follows. feed.xml downloads as feed.json or feed.jsonl, keeping the name it arrived with. Copy to clipboard gives you the same text.

What the conversion cannot carry

  • Namespace prefixes go. A merchant feed of g:id and g:title elements produces keys called id and title. Readable, and almost always what you wanted. The exception is a document that uses two namespaces with the same local name, where both write into one key.
  • Mixed content loses the surrounding words. <line>paid <amt>40</amt> today</line> yields an amt key holding 40, and paid and today are gone. An element that has child elements is read through those children only.
  • Depth runs out at three levels. Below three levels under the record element, a branch is written into its key as its own text and a note tells you in how many places that happened. Nothing is silently dropped, but the structure at the bottom is flattened to a string.
  • Repeated children share a key. Three category elements inside one product write into a single category key, and the last one wins. Repeating groups inside a record need reshaping the tool does not attempt.
  • Only the root's children are searched for records. An envelope like response, then body, then rows, then row has nothing repeating at the top, so paste the inner fragment. A document with exactly one record still converts and says the structure may not be tabular.
  • Broken XML stops the conversion. An unclosed tag returns the parser's own message with the line and column, rather than a half converted file. Comments and the XML declaration are ignored, and CDATA arrives as ordinary text.

Frequently Asked Questions

What happens to XML attributes?

They become ordinary keys, with no @ marker in front of them. An attribute on the record element keeps its bare name, so sku stays sku. An attribute on a nested element is prefixed with that element, so currency on a price becomes price.currency, and it is written before the element's own value.

Are namespace prefixes kept?

No. Prefixes are removed before the keys are built, so g:title becomes title and dc:creator becomes creator. That keeps the JSON readable at the cost of one collision: two elements sharing a local name across different namespaces land in the same key, and the one read later wins.

Why is the JSON flat instead of nested like the XML?

Because the output is built for records rather than documents. Nesting is preserved in the key names using dots, three levels below the record element, so a shipping block gives shipping.weight and shipping.origin. If you need a tree-shaped mirror of the document, this is not the right tool.

Does every object end up with the same keys?

Yes, and that is deliberate. The key list is the union of every record's fields, and a record that lacks one gets it explicitly as null. Reading the first object to learn the schema therefore gives you the right answer instead of a shape that half the file disagrees with.

How are numbers and booleans decided?

A value becomes a JSON number only when that number would print back as the identical text, so 2.1 is a number and 18.00 stays a string, as do 04002 and any digit run long enough to lose precision. Written in lower case, true and false arrive as real booleans. Dates are left alone as strings.

Is my file uploaded, and how large can it be?

Nothing is uploaded. Your browser's XML parser reads the document in the tab and the JSON is written there too. This page takes files up to 100 MB with no row cap and no daily quota; past that it hands you to the full editor, which streams instead.

Convert your XML to JSON

Paste it or drop the file. Pick an array or JSON Lines, copy the result, or download it under the name it came in with.

Back to the converter