Last updated: 2026-09-17
Paste XML and get formatted JSON output. XML attributes are prefixed with @_ by default. You can control attribute prefixing, array detection, namespace handling, and whether tag values are parsed as numbers/booleans. All processing happens in your browser.
What this tool does
Converts an XML document into formatted JSON. XML attributes are prefixed with @_ by default. Repeated child elements become JSON arrays. You can toggle namespace stripping and number/boolean parsing. The conversion uses fast-xml-parser and runs entirely in your browser.
Example
Input:
<user id="123" role="admin">
<name>Alice</name>
<email>alice@example.com</email>
</user>
Output:
{
"user": {
"@_id": "123",
"@_role": "admin",
"name": "Alice",
"email": "alice@example.com"
}
}
How XML maps to JSON
| XML construct | JSON representation | Example |
|---|---|---|
| Element with text | String value | <name>Alice</name> → "name": "Alice" |
| Attribute | Key with @_ prefix | <user id="123"> → "@_id": "123" |
| Repeated elements | JSON array | <item>A</item><item>B</item> → "item": ["A", "B"] |
| Text between elements | #text key | <a>Hello<b/></a> → "#text": "Hello" |
| Self-closing element | Empty string | <tag/> → "tag": "" |
| Namespace prefix | Preserved or stripped | <ns:user> → "ns:user" or "user" |
All values are strings by default. Enable Parse numbers and booleans to convert numeric and boolean-looking values to their JSON equivalents. Whitespace is handled for you: leading and trailing spaces inside every tag value are trimmed automatically, so <name> Alice </name> becomes "Alice". Whitespace between words inside a value is preserved.
Namespaces
XML namespaces are preserved in the output by default. An element like <ns:user xmlns:ns="http://example.com"> produces a key ns:user in the JSON. Enable Strip namespaces to remove the ns: prefix and the xmlns declarations, producing cleaner JSON when you do not need namespace information.
Mixed content
XML with mixed content (text and child elements interleaved) is handled, but the output structure may not perfectly represent the ordering. Text between elements is accessible via the #text key. For documents with complex mixed content (like XHTML), consider whether JSON is the right target format.
CDATA sections
CDATA sections are preserved as text content in the JSON output. The parser treats CDATA and regular text identically, so you do not need to handle them separately.
XML entity references
XML lets you escape characters that would otherwise break the markup. The converter decodes the standard entity set automatically because fast-xml-parser runs with entity processing enabled. You do not need to toggle anything.
&becomes&<becomes<>becomes>"becomes"'becomes'- Numeric character references such as
<and<both become<
Input:
<message>Tom & Jerry <3</message>
Output:
{
"message": "Tom & Jerry <3"
}
The decoded text is an ordinary string in the JSON, so downstream code reads it directly with no extra unescaping. One caveat: custom entities declared in a DTD (for example <!ENTITY company "Acme">) are not resolved. The parser does not load external DTDs, and this tool does not pass a custom entity map, so an unresolved entity triggers a parse error. Stick to the five built-in entities and numeric references, or expand your custom entities before pasting the XML.
Converting SVG to JSON
SVG is XML, so it converts like any other document. Attributes become @_-prefixed keys, nested elements become nested objects, and text inside an element lands under #text.
Input:
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<rect x="10" y="10" width="80" height="80" fill="blue"/>
<text x="50" y="55" font-size="12">Hi</text>
</svg>
Output (defaults, namespaces preserved):
{
"svg": {
"@_xmlns": "http://www.w3.org/2000/svg",
"@_width": "100",
"@_height": "100",
"rect": {
"@_x": "10",
"@_y": "10",
"@_width": "80",
"@_height": "80",
"@_fill": "blue"
},
"text": {
"@_x": "50",
"@_y": "55",
"@_font-size": "12",
"#text": "Hi"
}
}
}
A few notes specific to SVG. The default xmlns declaration becomes @_xmlns because it is an attribute like any other. Attribute names keep their original characters, so font-size becomes @_font-size with the hyphen intact. Coordinate and size values stay strings unless you enable Parse numbers and booleans, in which case "100" becomes the number 100. For most drawing code you want coordinates as strings, so leave parsing off. SVG with many nested <g> groups produces deeply nested JSON; if you only need a flat list of shapes, strip namespaces first and post-process the result to walk the tree.
Converting SOAP responses
SOAP envelopes wrap the actual payload in Body and Header elements and almost always carry namespace prefixes. The converter handles the nesting directly; the only decision is whether to keep the prefixes.
Input:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<m:GetPriceResponse xmlns:m="http://example.com/pricing">
<m:Price>42.50</m:Price>
<m:Currency>USD</m:Currency>
</m:GetPriceResponse>
</soap:Body>
</soap:Envelope>
Output with namespaces preserved:
{
"soap:Envelope": {
"@_xmlns:soap": "http://schemas.xmlsoap.org/soap/envelope/",
"soap:Body": {
"m:GetPriceResponse": {
"@_xmlns:m": "http://example.com/pricing",
"m:Price": "42.50",
"m:Currency": "USD"
}
}
}
}
Output with Strip namespaces enabled:
{
"Envelope": {
"Body": {
"GetPriceResponse": {
"Price": "42.50",
"Currency": "USD"
}
}
}
}
For SOAP, stripping namespaces is usually what you want. The prefixes carry no data, and removing them lets you write result.Envelope.Body.GetPriceResponse.Price instead of bracket notation with soap: and m:. If two namespaces define the same local name and stripping would collide them, keep namespaces on and disambiguate by prefix. Note that 42.50 stays a string unless you enable parsing; if your consumer does math on the price, turn Parse numbers and booleans on or cast it yourself.
Parsing numbers and booleans
By default every tag value and attribute value is a string. Enable Parse numbers and booleans and the converter inspects each value: if it matches a number it becomes a JSON number, and if it equals true or false it becomes a JSON boolean. Internally this sets parseTagValue and parseAttributeValue together, so the same toggle governs both tag text and attribute text.
Input:
<sensor id="3" active="true">
<temperature>21.5</temperature>
<humidity>64</humidity>
<label>sensor-3</label>
</sensor>
Output with parsing enabled:
{
"sensor": {
"@_id": 3,
"@_active": true,
"temperature": 21.5,
"humidity": 64,
"label": "sensor-3"
}
}
label stays a string because sensor-3 is not a valid number. This convenience has a cost. Identifiers that look like numbers lose their original representation: an id of 007 becomes the number 7, dropping the leading zeros. A boolean attribute written as true or false becomes a real boolean, but attributes written as 1 and 0 stay strings because the parser does not coerce those to booleans. Values written in scientific notation like 1e3 become numbers, which is usually welcome. When in doubt, leave parsing off and cast values explicitly in your consuming code where the intent is unambiguous.
What the converter drops automatically
Some XML constructs carry no useful information for JSON and are removed before the object tree is built. You do not toggle these.
- The XML declaration
<?xml version="1.0" encoding="UTF-8"?>is discarded (ignoreDeclaration). - Processing instructions such as
<?xml-stylesheet type="text/xsl" href="style.xsl"?>are discarded (ignorePiTags). - Comments
<!-- note -->are not preserved in the output tree. - The DOCTYPE block is not loaded, and external DTDs are never fetched.
The practical effect is that you can paste a full document, including its prolog and stylesheet instructions, without first trimming them by hand. The output starts at your root element. If you rely on the encoding declared in the prolog, note that the converter assumes the input is already a Unicode string; it does not transcode bytes.
Force array option
XML has no way to mark an element as a list of one. <items><item>A</item></items> and <items><item>A</item><item>B</item></items> look structurally different: the first yields a single object, the second yields an array. Code that always expects an array breaks on the single-item case.
The converter auto-detects repeated siblings and turns them into arrays. For tags that appear once but must still be arrays in your consumer, use the Force array option. It takes a list of tag names; every occurrence of those tags is wrapped in an array, even when there is only one.
Input:
<orders>
<order id="1">A</order>
</orders>
Without force array:
{
"orders": {
"order": {
"@_id": "1",
"#text": "A"
}
}
}
With order added to Force array:
{
"orders": {
"order": [
{
"@_id": "1",
"#text": "A"
}
]
}
}
List only leaf or item-level tag names you know are logically arrays: item, order, line, phone, email, attachment. Forcing array on a container like orders wraps the whole container, which is rarely what you want. The match runs on the exact tag name after namespace stripping, so if you enable Strip namespaces, list the un-prefixed names. Repeated elements are already arrays automatically, so you only need force array for tags that might appear once.
Do it in code
Node.js (fast-xml-parser):
const { XMLParser } = require('fast-xml-parser');
const parser = new XMLParser({ attributeNamePrefix: '@_', textNodeName: '#text' });
const result = parser.parse(xmlString);
console.log(JSON.stringify(result, null, 2));
Python (xmltodict):
import xmltodict, json
doc = xmltodict.parse(xml_string)
print(json.dumps(doc, indent=2))
Note: xmltodict uses @ as the default attribute prefix (not @_).
CLI (xml2json):
cat data.xml | xml2json | jq '.'
Install with npm install -g xml2json-command-line, or use yq -p xml -o json '.' data.xml with the Go-based yq.
Choosing options for large feeds
Big feeds such as RSS, Atom, and product catalogs share a shape: one root, a repeating item element, and attributes carrying metadata. A sensible default configuration for these is to strip namespaces, keep attributes with the @_ prefix, leave parsing off, and force array on the repeating item tag. With namespaces gone you avoid long dotted keys; with parsing off you avoid corrupting identifiers and publication dates that happen to be numeric; with force array on the item tag your loop always receives a list even when a feed contains a single entry.
Attributes are usually few and meaningful in feeds (GUIDs, URLs, timestamps), so keeping them is worth the prefix. If a feed uses attributes only for namespace declarations and you have already stripped namespaces, enabling Ignore attributes produces a smaller object with no @_ keys at all. Measure both shapes on your actual data before committing; the difference in object size and traversal cost can be significant when you process thousands of entries.
Edge cases handled
- Repeated child elements automatically become JSON arrays.
- Self-closing tags (
<tag/>) and empty elements (<tag></tag>) become empty strings. - XML declarations (
<?xml ...?>) and processing instructions are ignored. - Namespace prefixes can be stripped with the Strip namespaces option.
- Attributes can be ignored entirely with the Ignore attributes option.
- Unclosed tags are detected and reported as an error.
- Non-XML input (missing
<at start) is detected and reported.
Validation and errors
The converter validates before and during parsing. If the input does not begin with a < character it rejects the document early with a message that the input does not appear to be valid XML. During the parse, fast-xml-parser reports structural problems: unclosed tags, mismatched closing tags, malformed attributes, and unresolvable entities. Each error surfaces as a message in the output panel instead of JSON.
The validator does not enforce a schema. There is no XSD or DTD checking, so a structurally valid document whose content violates your business rules parses without complaint. Length limits, required fields, allowed enumerations, and cross-field constraints are your responsibility downstream. The converter also does not fetch external resources, so external entity references and external DTD subsets fail rather than silently expanding, which keeps the operation safe and offline.
Round-tripping back to XML
If you convert XML to JSON and then feed that JSON into the JSON-to-XML tool, you get a document back, but it is not byte-identical to the original. Information is lost or reshaped in one direction. The XML declaration, comments, processing instructions, and DOCTYPE were dropped on the way in, so they cannot reappear. Attribute order within a tag is not preserved by JSON objects, so the regenerated tag lists attributes in an arbitrary order. Mixed content collapses to a single #text string, so the relative positions of text and child elements are gone. Whitespace between attributes and indentation are not retained.
For data-shaped XML such as configuration files, API payloads, and feeds where every element contains either text or child elements but not both, the round trip is close enough for diffing and editing. For document-shaped XML such as XHTML, DocBook, or hand-authored prose with inline markup, treat the conversion as one-way.
Common mistakes when converting XML to JSON
- Reading attributes without the prefix. Attributes live under
@_name, notname.user.idisundefined; useuser['@_id']. If the prefix is inconvenient, enable Ignore attributes to drop attributes entirely, or change the prefix in the options. - Assuming a child is always an array. A single
<item>becomes an object or string, not an array. Looping code that callsitems.item.forEachthrows when there is one item. Add the tag to Force array, or guard withArray.isArraybefore iterating. - Expecting typed attribute values. Attributes are strings unless Parse numbers and booleans is on, and the same toggle controls both tag values and attribute values. A price of
"42.50"stays a string by default. - Losing leading zeros. With parsing on,
<zip>02134</zip>becomes the number2134, dropping the leading zero. Keep parsing off for identifiers, zip codes, phone numbers, and any value that is numeric in shape but textual in meaning. - Trusting element order in mixed content. JSON objects do not guarantee key order, and text interleaved with child elements collapses into
#text. If order matters (XHTML, rich text, document prose), JSON is a lossy target. - Forgetting to strip namespaces. Keys like
soap:Bodyand@_xmlns:soapwork but make access verbose and break simple dot notation. Enable Strip namespaces unless you need the prefixes for disambiguation. - Treating empty elements as null.
<tag/>becomes an empty string"", notnull. Code that checksvalue === nullmisses empty elements; checkvalue === ""or truthiness instead. - Ignoring trimmed whitespace. The converter trims leading and trailing whitespace inside every tag value, so
<name> Alice </name>becomes"Alice". Inner whitespace is preserved, but surrounding whitespace is not. Do not expect to round-trip pretty-printed XML through this converter unchanged. - Confusing the text node key. Mixed text lives under
#text, not under the element name. An element like<a>Hello<b/></a>exposes its text asa['#text'], whileaitself is the object holding#textand the childb.