← All guides

XML to JSON Conversion: Attributes, Mixed Text, and Why Repeated Tags Become Arrays

JSON has no concept of an attribute and no concept of an element that has both text content and child elements at once — XML has both, routinely. Any XML-to-JSON converter has to invent a convention to bridge that gap, and different tools invent different ones. This converter follows the convention used by fast-xml-parser, a widely-used library, specifically so the output shape is familiar if you've worked with that library before.

The three rules

<user id="42" active="true">
  <name>Ada</name>
  <name>Grace</name>
  Some trailing note
</user>

↓

{
  "user": {
    "@_id": "42",
    "@_active": "true",
    "name": ["Ada", "Grace"],
    "#text": "Some trailing note"
  }
}

Attributes become keys prefixed with @_id="42" becomes "@_id": "42". A tag that appears more than once at the same level (<name> twice) becomes a JSON array instead of overwriting itself; appearing once, it stays a plain value. And text sitting alongside attributes or child elements — content XML calls "mixed content" — is pulled out into a "#text" key rather than being dropped, since there's nowhere else in the JSON shape for loose text next to structured children to go. An element with neither attributes nor children collapses all the way down to a plain string, skipping the wrapper object entirely — that's why <name>Ada</name> becomes just "Ada", not { "#text": "Ada" }.

Attribute values are always strings, even "true" and "42"

Advertisement

Notice "@_active": "true" above stays a JSON string, not a boolean — XML attribute values are text by definition, and this converter doesn't attempt to guess that an attribute that looks like a boolean or a number should be coerced into one. That's the safer default for the same reason CSV-to-JSON conversion elsewhere in this suite leaves values as strings: coercion guesses wrong often enough (a version string like "1.20" silently losing its trailing zero if parsed as a number) that leaving the decision to you is more honest than guessing.

Comments don't make it into the JSON output

XML comments (<!-- like this -->) and CDATA sections are both handled differently here than in this suite's dedicated XML Formatter, which preserves both distinctly because a pure beautifier's job is round-tripping XML back to XML. Converting to JSON is a different kind of operation — there's no JSON equivalent of "this was a comment" or "this was CDATA rather than plain text", so comments are dropped entirely and CDATA content is merged into the same "#text" bucket as ordinary text. That's a deliberate scope difference between the two tools, not an oversight in one of them — if you need a comment-preserving round trip, that's what the XML-to-XML formatter is for.

Going back to XML needs exactly one root key

JSON allows multiple top-level keys; XML requires exactly one root element. Converting JSON back to XML checks for this up front and rejects a document with zero or more than one top-level key with an explicit error, rather than guessing which key should become the root or silently wrapping everything in a synthetic wrapper element you didn't ask for.

Advertisement

Try the XML ↔ JSON Converter