XML to JSON Converter
Convert XML to JSON and JSON back to XML in your browser, with attributes kept as "@" keys and repeated tags turned into arrays.
How to Convert XML to JSON
Paste XML into the left panel and the JSON appears as you type. Parsing uses your browser's own XML parser — the same one that handles XHTML and SVG — so it validates well-formedness properly and reports the line where a document breaks, rather than accepting something malformed and producing quiet nonsense.
Everything runs locally. Your document is never uploaded, logged, or sent to a server, which matters because the XML developers most often need to convert is a SOAP response or a partner feed containing credentials, order records, or personal data.
The Mapping Convention
XML and JSON do not describe the same shape of data, so every converter has to invent a convention. This one is stated up front and applied without exception:
| XML | JSON |
|---|---|
Attribute id="bk101" | "@id": "bk101" |
| Element with only text | The string itself |
| Element with text and attributes or children | "#text" alongside the other keys |
| A tag appearing more than once | An array |
Empty element <a/> | null |
<![CDATA[...]]> | Merged into the element's text |
<book id="bk101">
<title>XML Guide</title>
<price>44.95</price>
</book>
{
"book": {
"@id": "bk101",
"title": "XML Guide",
"price": "44.95"
}
}
The @ prefix exists because XML has two ways to attach a value to an element and JSON has one. Without it, an attribute named title and a child element named title would collide. The prefix keeps them distinct and makes the conversion reversible — switch to JSON → XML and any key beginning with @ becomes an attribute again.
Three Things That Make XML to JSON Genuinely Hard
<price>44.95</price> becomes "44.95", not 44.95. This is not a limitation of the tool — XML has no type system at all. Types live in an external XSD schema that is not part of the document, so a converter reading the document alone has nothing to work from. Converters that guess will eventually turn a product code like 0012 into 12. Cast on your side, where you know the schema.
items.item[0], and an order with one gives you items.item — and code written against the first crashes on the second. This is the single most common bug in XML-to-JSON pipelines. Defend against it by normalising on read: const list = [].concat(parsed.items.item ?? []).
DOCTYPE is not carried across. If the document is a contract with an external system, keep the XML as the source of truth and treat the JSON as a view of it.
XML to JSON Examples
Three documents that cover most of what an XML to JSON converter meets in practice. Each output below was produced by pasting the XML into the tool above.
An RSS item. The guid element has both an attribute and text, so its text moves to #text. The CDATA section arrives as a plain string with its contents untouched: the & inside it stays &, because CDATA is not entity-decoded.
<rss version="2.0">
<channel>
<title>Stackiox Blog</title>
<item>
<title>XML to JSON in practice</title>
<link>https://example.com/xml-json</link>
<description><![CDATA[<p>Attributes, arrays & CDATA.</p>]]></description>
<guid isPermaLink="false">post-42</guid>
</item>
</channel>
</rss>
{
"rss": {
"@version": "2.0",
"channel": {
"title": "Stackiox Blog",
"item": {
"title": "XML to JSON in practice",
"link": "https://example.com/xml-json",
"description": "<p>Attributes, arrays & CDATA.</p>",
"guid": {
"@isPermaLink": "false",
"#text": "post-42"
}
}
}
}
}
A SOAP response. The namespace prefix stays on the key, the namespace declarations become @xmlns attributes, and every value, including the total and the quantity, is a string. Switch to JSON → XML and paste the output back: this document comes out identical to the input, plus the XML declaration.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetOrderResponse xmlns="urn:shop">
<order id="1001" status="shipped">
<total currency="EUR">89.90</total>
<item sku="A-17">2</item>
</order>
</GetOrderResponse>
</soap:Body>
</soap:Envelope>
{
"soap:Envelope": {
"@xmlns:soap": "http://schemas.xmlsoap.org/soap/envelope/",
"soap:Body": {
"GetOrderResponse": {
"@xmlns": "urn:shop",
"order": {
"@id": "1001",
"@status": "shipped",
"total": {
"@currency": "EUR",
"#text": "89.90"
},
"item": {
"@sku": "A-17",
"#text": "2"
}
}
}
}
}
}
Mixed content. Text interleaved with child elements is the one shape JSON cannot hold faithfully. <p>Hello <b>world</b>, again<!-- note --><br/></p> converts to:
{
"p": {
"b": "world",
"br": null,
"#text": "Hello , again"
}
}
The text fragments are joined into one #text value, their position relative to <b> is lost, and the comment is dropped. That is fine for data documents and wrong for prose. If the XML is really XHTML or a document format, convert it with the HTML to Markdown converter instead.
Namespaces
Namespace-qualified tags keep their prefix exactly as written: <soap:Envelope> becomes the key "soap:Envelope", and the declaration itself survives as the attribute key "@xmlns:soap". Nothing is resolved or rewritten, so a round trip through JSON and back produces a document with the same prefixes it started with.
This is the pragmatic choice rather than the theoretically correct one. Namespace prefixes are arbitrary labels — two documents using soap: and s: for the same namespace URI are equivalent to a validating parser but produce different JSON keys here. If you are consuming feeds from multiple vendors, match on the local name rather than the full prefixed key.
Converting JSON to XML
Switch direction and the convention runs backwards: @ keys become attributes, #text becomes the element's text content, arrays become repeated tags, and null becomes a self-closing element. Output is indented two spaces and carries an XML declaration.
A JSON object with exactly one top-level key uses that key as the root element, since XML permits exactly one. Several top-level keys are wrapped in a <root> element instead, because there is no other legal option.
Element names are validated rather than silently repaired. XML names cannot contain spaces and must begin with a letter or underscore, so a key like "first name" is reported as an error instead of being quietly rewritten to first_name — a rename would produce a document that no longer matches the schema it was headed for, and you would find out much later.
Escaping is handled for you, and only where the specification requires it: &, <, and > are escaped in element text, and attribute values additionally escape the double quote that would otherwise close them. A quote or apostrophe sitting in ordinary text is left alone, because escaping it would be noise rather than correctness. A document converted to JSON and straight back comes out byte-for-byte identical, entities included.
Convert XML to JSON in Code
To convert XML to JSON inside an application rather than in the browser, use a library that follows the same convention, so the JSON you prototype here matches what your code produces. All three examples below convert this file:
<order id="1001" status="shipped">
<total currency="EUR">89.90</total>
<item sku="A-17">2</item>
</order>
Python — xmltodict uses the same @ and #text convention as this tool, so its output is identical to the converter's:
import json, xmltodict
with open("order.xml") as f:
data = xmltodict.parse(f.read())
print(json.dumps(data, indent=2))
{
"order": {
"@id": "1001",
"@status": "shipped",
"total": {
"@currency": "EUR",
"#text": "89.90"
},
"item": {
"@sku": "A-17",
"#text": "2"
}
}
}
Pass force_list=("item",) to xmltodict.parse to make item an array even when there is only one, which removes the one-versus-many bug described above.
Node.js — fast-xml-parser needs three options to match. With its defaults, the file above becomes {"order":{"total":89.9,"item":2}}: the attributes are gone and 89.90 has been turned into the number 89.9.
import { readFileSync } from 'node:fs';
import { XMLParser } from 'fast-xml-parser';
const parser = new XMLParser({
ignoreAttributes: false, // the default drops every attribute
attributeNamePrefix: '@',
parseTagValue: false, // the default turns "0012" into 12
isArray: (name) => name === 'item',
});
const data = parser.parse(readFileSync('order.xml', 'utf8'));
With those options the output matches the tool's, except that item is always an array and attribute keys come after the other keys, which JSON consumers do not care about.
JavaScript in the browser — no library needed. DOMParser is the parser this tool uses, and this short function applies the same mapping. On 1,000 randomly generated documents without mixed content it produced exactly the same JSON as the converter:
function toJson(el) {
const obj = {};
for (const a of el.attributes) obj['@' + a.name] = a.value;
for (const child of el.children) {
const v = toJson(child);
const k = child.nodeName;
obj[k] = Object.hasOwn(obj, k) ? [].concat(obj[k], v) : v;
}
const text = el.children.length ? '' : el.textContent.trim();
if (!Object.keys(obj).length) return text || null;
if (text) obj['#text'] = text;
return obj;
}
const doc = new DOMParser().parseFromString(xmlString, 'application/xml');
if (doc.querySelector('parsererror')) throw new Error('Invalid XML');
const json = { [doc.documentElement.nodeName]: toJson(doc.documentElement) };
Object.hasOwn matters here: a plain in check would treat an element named <constructor> or <toString> as already present, because those names exist on every object's prototype.
Why This Conversion Still Comes Up
XML was declared legacy years ago and remains everywhere it was already installed. Three cases account for most conversions.
SOAP and enterprise APIs. Banking, insurance, logistics, government, and healthcare integrations are overwhelmingly SOAP, and they are not being rewritten. Converting the envelope to JSON at the edge lets the rest of your codebase work in one format. The credentials in those envelopes are exactly why a converter that uploads your payload is the wrong tool.
RSS, Atom, and sitemaps. Feed formats are XML by specification, and the body of each item is usually HTML wrapped in CDATA; to republish an item in a Markdown-based blog or docs site, paste that HTML into our HTML to Markdown converter. Anything that ingests feeds — readers, aggregators, monitoring — converts them on the way in. A sitemap is only useful once crawlers know where it is, which is one Sitemap: line in robots.txt; our robots.txt generator writes that line and lets you test which paths each crawler may fetch. Feed polling is scheduled work by nature, and polling too aggressively is the fastest way to get rate-limited by a publisher; our cron job generator turns an interval into a valid expression and shows you when it would actually fire.
Configuration in older toolchains. Maven pom.xml, Android manifests, .NET web.config, and Ant build files are XML. Converting to JSON makes them scriptable with ordinary tooling; if you are migrating that config to a modern format, our YAML ↔ JSON converter is the second half of the trip, since most current toolchains expect YAML.
Checking the Result
XML documents nest far more deeply than most JSON, and a converted SOAP response frequently arrives six or seven levels down inside envelope and body wrappers. Paste the output into our JSON formatter and validator — its depth and key counts tell you immediately whether you are looking at the payload or at the envelope wrapped around it. If the result is a list of records headed for a spreadsheet, JSON ↔ CSV will flatten it into columns.
Frequently Asked Questions
DOMParser. Nothing is transmitted, stored, or logged, so it is safe to paste SOAP envelopes containing credentials.[].concat(value) on read — this is the classic XML-to-JSON pitfall.@ keys, repeated tags become arrays, and CDATA becomes a string. Copy the result, or press Use as input to convert it back to XML. The conversion runs in your browser, so nothing is uploaded.Hello <b>world</b>, again loses the order of its text fragments.xs:element, xs:complexType and their attributes. That is not the same as a JSON Schema. Translating XSD types and constraints into JSON Schema needs a dedicated schema converter, and some XSD features have no JSON Schema equivalent.