Free & Private Online Browser Utility

XML to JSON Converter (Free, No Upload)

This page reads an XML document and writes it as a JSON value, using the parser that is already inside your browser to check that the markup is well-formed and a mapping written out here to decide what each kind of node becomes. That division matters: parsing an XML file is a solved problem, while turning a tree of elements, attributes and mixed text into objects and strings is a set of decisions, and a converter that keeps its decisions to itself produces output that looks arbitrary. A name appearing more than once among its siblings becomes an array and a name appearing once becomes a single value, with a switch to make every child a list so a reader can iterate without checking the count first. Attributes become keys carrying a chosen prefix, or are left out entirely on request, and the page counts what it leaves out. Text that shares an element with anything else is written under a dedicated key, and where that text was interleaved with markup the order is stated as lost rather than hidden. Type inference is off by default because XML gives every value as characters and only the schema — which this page cannot see — knows that one of them is a number; switching it on applies the same round-trip test the CSV converter uses, so a code beginning with zero is never quietly shortened. Comments, processing instructions and CDATA markers cannot survive the trip and are counted. Nothing is uploaded; the document is parsed in the tab you pasted it into.

How to use

  1. Step 1: Paste the markup or open a file

    Copy an XML export, an RSS feed or a config file into the pane, or press Load .xml. The file is read into this page and stays there.

  2. Step 2: Choose what an attribute is worth

    Keep them as prefixed keys, move them behind a different prefix, or leave them out. The same row of controls handles namespace prefixes, array shapes and whether text should be read as numbers.

  3. Step 3: Read the loss report before using the value

    Arrays formed, merges, mixed text, dropped nodes and left-out attributes are each stated with a number. Copy the JSON or download the file once those numbers describe the document you meant.

Options explained

Attribute policy

Writes attributes as keys carrying @ or a dash, or omits them, counting the ones omitted.

When to use: The prefixed default keeps an attribute distinguishable from a child of the same name, which is what most feeds and configuration files need. Leave them out only when the values genuinely do not matter, because nothing else in the output will mention what was thrown away.

Every child as an array

Wraps each child name in a list even when it occurs once, instead of only when it repeats.

When to use: Turn it on for code that maps over a collection. With it off, a document gains or loses one record and the value changes between a list and a single object, which is the most common way an XML reader breaks without an error.

Namespace prefixes

Keeps names such as soap:Envelope exactly as written, or removes the prefix and reports every key that then had to merge.

When to use: Keep them when two vocabularies share a local name in the same document. Strip them when you are reading one namespace and the prefix is noise, and check the merge line afterwards.

Type inference

Casts a text value to a number only when the number prints back as the identical characters, and recognises the words true and false; leaves everything else a string.

When to use: Off is the honest default for markup. Switch it on when the values feed arithmetic, and stay off for identifiers, version numbers and any code that begins with a zero.

Whitespace between elements

Treats indentation and line breaks between tags as formatting and drops them, or keeps them as data.

When to use: The default is right for any pretty-printed file. Keep whitespace when the padding inside a text run is itself meaningful, and expect values to arrive with newlines attached.

Worked example

Input: <shelf lang="en"> <book id="k14">Legacy</book> <book id="p9">Half <b>across</b> the road</book> <withdrawn/> </shelf>

Output: { "shelf": { "@lang": "en", "book": [ { "@id": "k14", "#text": "Legacy" }, { "@id": "p9", "b": "across", "#text": "Half the road" } ], "withdrawn": "" } }

Two elements sharing a name arrived as a list, the attributes became prefixed keys, the empty element became an empty string, and the sentence broken by a formatting tag came back as one run with the gap where the tag had been.

Tips & limits

  • Check whether a name occurs once in your document before writing code that reads it as a list. With the default settings that value is an object, and the failure appears at runtime as a method that does not exist on a plain string.
  • Turn on the every-child switch when converting a feed whose record count changes daily, so the shape stays the same on a day when only one item was published.
  • Keep type inference off for files whose values are identifiers. A version written as 1.10 and a code written as 007 both stop being themselves the moment they are numbers.

Common problems

The output is a refusal that mentions a parse error

The browser's own parser judged the markup, and its sentence is quoted in full. An unmatched closing tag, an undeclared namespace prefix and a second root element are the three usual causes; fix whichever line the message names and the conversion runs again.

A field I expected is missing from the JSON

Read the report lines. An element with an unparseable shape is not silently dropped here, but an attribute can be left out by the policy you chose, and a value written entirely as a comment is not data to a JSON document.

Frequently Asked Questions (FAQ)

Does an element that appears only once become an array?

No, and that asymmetry is deliberate. Repetition is the only way XML expresses a list, so a name carrying more than one sibling becomes an array while a name carrying one becomes a single value. The switch that puts every child inside a list anyway exists because that rule is inconvenient for code: it means a document with one record and a document with three records have different shapes. The report states how many arrays were formed, so the shape you got is visible rather than assumed.

Where do attributes go, and can they collide with elements?

Into keys carrying the prefix you chose, which is why an attribute and a child element with the same local name stay apart in the output. With the policy set to leave attributes out they are gone completely — not renamed, not moved under a wrapper key — and the count in the report is the only evidence that the document had them.

What happens to comments, processing instructions and CDATA?

Comments and processing instructions have no representation in JSON, so they are discarded and counted, at the document level as well as inside the tree; a stylesheet instruction that made the original file render is one of the things that goes. CDATA is the opposite case: its characters are ordinary text and they survive, but the marker recording that they were written unescaped does not, because a JSON string carries no such flag.

Why does some text arrive under a key called #text?

Because the element could not be a single string. Any element carrying an attribute or a child alongside its text has to become an object, and the text needs a name of its own; that name is reserved by this page rather than being another of your element names. The other half of the same rule is a loss worth knowing about: text written around a formatting tag is concatenated, because an object has no ordering between one value and its sibling, so the position a tag held in a sentence is not recoverable from the JSON.

Can a value in my JSON have come from the document type instead of the element?

Yes, in two ways, and both are the parser working correctly. An entity reference is replaced by the text the document type declares before this page sees the tree, so the characters in the output came from a declaration rather than from the element holding them — those substitutions are counted. And an attribute default written in a list declaration is applied to elements that never mention it, so a key can appear in the JSON that has no counterpart in the tags. The document type itself is not fetched from anywhere: an address in it stays unvisited.

Is the XML validated against a schema?

No. Well-formedness is what the browser's parser checks, and that is the only judgement this page makes: whether the markup can be read as a tree. Whether the tree satisfies a published schema, an RSS dialect or a vendor's expectations is a different question with different tools, and a document can convert cleanly and still be wrong for the system waiting for it.

How large a file can this page handle?

There is no upload and therefore no quota, so the limit is the memory and the single thread of the tab. Parsing and mapping both run in the page, and a document big enough to make the interface hesitate will hesitate here rather than on a server queue. The output pane paints only the first twenty thousand characters so a very large result cannot lock the display, while copy and download still carry everything. A file that size belongs in a script on the machine beside you.

Guides that cover this tool

Parsing is the browser's job; deciding what it means is the interesting part

An XML parser is a well-understood machine, and this page does not write one: the document is handed to the DOMParser already in your browser, the same one that checks your files for well-formedness when they load. What a converter has to add is a set of opinions, because a tree of elements, attributes and text does not map onto objects, keys and strings by any single rule. The page states each opinion, lets you overrule the ones worth overruling, and then reports what the combination did to your document.

Repetition is the only list XML has

Three sibling <entry> elements are a list; two are a list; one is not, and that is the awkward case every XML-to-JSON tool gets asked about. The rule here is the usual one — a name that appears more than once among its siblings becomes an array, a name that appears once becomes a single value — and a switch that makes every child an array regardless, which is what a consumer that reads .map() needs. Without that switch there is no way to tell an array of one from a scalar after the fact, because both arrive at the same document; the switch does not change what is in your file, only how predictable the JSON is. The count of arrays actually formed is printed with the result, so the shape you got is not a guess.

Attributes have no JSON equivalent, so you choose where they go

An attribute is a name-value pair attached to an element rather than a child of it, and a JSON object has only name-value pairs. The default writes them as keys carrying @ — <entry id="k14"> becomes {"@id": "k14"} — because a prefix keeps an attribute from colliding with a child of the same name and marks it as something the source did not put in the tree as a node. Switching to a dash is the same rule with a different character, for codebases that already use one; switching to leaving them out is the only option that genuinely discards data, and the page counts the attributes it drops so the loss has a number attached to it.

Mixed content, and what a string cannot hold

An element that carries both text and child elements is the case JSON handles worst, and it is common in prose-bearing markup. The text goes under #text beside the children's keys. What cannot be preserved is the interleaving: text that arrived in two pieces around a child is concatenated, because a JSON object has no order between a value and a sibling key. A document where the position of that text mattered — a footnote marker mid-sentence, for instance — needs a representation with ordered children, and this page will not pretend its output is one.

Comments, processing instructions and the doctype have no representation in JSON at all, so they are left out and counted. A stylesheet instruction that made the original render is gone; a comment that held the only note about why a field exists is gone too, and the report says how many of each so nobody finds out from a diff. CDATA is the mirror case: its characters are real text and they survive, but the marker that said "these characters were not markup" does not, because a JSON string carries no such flag.

Namespaces are kept unless you ask otherwise

<soap:Envelope> reads as a key called soap:Envelope by default, which is ugly and honest. Stripping prefixes is one switch, and it has a consequence the page reports rather than hides: two elements from different namespaces that shared a local name now share a key, so they merge into one array, and the merge is listed with the names that produced it. An empty result is also worth knowing about — a document whose only content was a namespace declaration has nothing left after stripping.

Text is data, and by default it stays text

XML has no types. Every value in a document is a run of characters, and the string 2 in a quantity element is not more of a number than the string 2 in a version element. So the output quotes everything unless you ask for inference, and when you do the rule is the narrow one used elsewhere on this site rather than a permissive cast: a value becomes a number only when reading it as one and printing it back gives exactly the characters that were there. That is what keeps a code like 007 a string, and an identifier of seventeen digits from arriving rounded, and it is the same predicate the CSV converter applies to a cell, so the two pages cannot disagree about what a number is. The words true and false become booleans in any capitalisation; yes, Y and 1 do not, because a column of one-letter flags is not a column of booleans by consensus.

An element written as <gift/> or <gift></gift> has no text. With inference off it becomes an empty string; with inference on it becomes null, which is the JSON word for "there is no value here" and the one thing XML had to express with absence. Whitespace between elements is treated as formatting and dropped, because indentation is not content in every document anyone has ever pretty-printed; the switch that keeps it exists for the rarer case where a text run's padding is the point.

What to check before this JSON goes anywhere

The status line under the pane gives the element count, the attributes kept or dropped, how deep the tree went and the size of the text produced. The lines above it are the loss report: arrays formed, merges, mixed content, dropped nodes. Two of those are the ones that bite in practice. The first is a name that appeared once in your file and therefore arrived as a single object rather than a one-item array — code that iterates it throws. The second is #text appearing inside a value you expected to be a plain string, which happens the moment an element gains a child element it did not used to have, and is the most common way a markup change breaks a reader without breaking the parser.

Round-tripping back to XML is a different document, and it is worth saying so before anyone relies on it. The reverse converter writes repeated elements from arrays, but nothing in JSON restores a comment, an attribute needs a prefix convention to be recognised as one, and the declaration line is a setting rather than a memory. This page turns markup into data you can read with code; it is not a serialiser that promises byte-for-byte reversibility.

What the parser had already done before this page saw the tree

Two things arrive in the JSON that were not literally in the markup, and both are the browser's parser doing its job. An entity reference written in an element is replaced by the parser with the text the document type declares for it, so &nick; shows up as whatever nick stands for, and the report counts how many of those substitutions were made. A default value on an attribute declaration appears on the element as an attribute the source file never wrote — a reader comparing the JSON against the text of the file will find a key there that is not in the tags. Neither is a defect in the conversion. Both are the reason a converted document is a read of the file rather than a transcription of it, and the second one is why a document with a DTD can produce JSON that does not match a grep of its own characters.

Nothing is fetched. The parser works on the text in the pane, so a document type that points at an address keeps that address unvisited, and no external file can change what the conversion produces.

Nothing leaves the tab

There is no upload step and no queue: the document is parsed by this page, walked in memory, and stringified here, which is what makes the address bar safe to carry settings on. ?attrPolicy=drop can reopen the page on that choice; no part of your XML is written into a URL, and a file loaded from disk never travels anywhere.

Files never leave your device; calculations and transformations happen locally.

Last reviewed: 2026-11-03