9 min read

How to Convert XML to JSON Without Losing Data

Learn how to convert XML to JSON without losing attributes, text nodes, or array structures, avoid the singleton list trap, and prevent data corruption.

When modernizing legacy systems, ingesting SOAP service responses, parsing RSS feeds, or integrating with enterprise ERP gateways, developers must routinely transform XML documents into JSON. Modern web applications and frontend frameworks like React, Vue, and Node.js operate natively on JSON. However, naive conversions frequently discard attributes, collapse single-element arrays into scalar objects, truncate leading zeros from postal codes, or erase mixed text content.

To preserve the XML values most applications depend on, adopt deterministic conventions: keep attributes under a dedicated prefix such as @, isolate element text with a key such as #text, retain primitive values as strings until explicitly cast, and normalize fields that your schema defines as arrays. A generic XML-to-JSON conversion is not a byte-for-byte reversible transformation because XML also has comments, processing instructions, CDATA boundaries, and ordered mixed content.

You can test and convert any document immediately with ToolPorta's XML to JSON Converter directly in your browser. This technical guide explains why the XML and JSON data models clash, presents a battle-tested 5-step conversion workflow, details solutions for the "singleton array" trap and attribute collisions, analyzes security concerns like XML External Entity (XXE) attacks, and compares both formats in an architectural reference table.

Why converting XML to JSON causes data loss#

XML and JSON were invented with different goals, creating an architectural impedance mismatch when converting from one to the other.

XML, defined by the W3C Extensible Markup Language 1.0 Specification, was designed as an extensible document markup language capable of handling structured records, narrative prose, and metadata simultaneously. JSON, defined under RFC 8259, was designed as a minimal, language-agnostic data interchange format strictly modeling key-value pairs and arrays.

Data loss occurs during conversion because XML possesses structural concepts that JSON lacks:

  1. Attributes vs. Child Elements: In XML, an element can store information both as attributes within its opening tag (<transaction id="tx_99" status="cleared">) and as nested child elements (<amount>450.00</amount>). JSON objects only have uniform key-value pairs. If a converter ignores attributes, critical metadata vanishes.
  2. Ambiguous List Semantics (The Array Problem): XML has no native list syntax. A list is represented simply by repeating sibling tags: <item>A</item><item>B</item>. When an XML list contains only one item (<item>A</item>), naive parsers cannot tell whether <item> was intended to be an array or a single property.
  3. Mixed Content and Inner Text: An XML element can contain both raw character data and child tags within the same scope: <note>Payment received on <date>2026-10-01</date> via wire.</note>. In JSON, a key's value must be either a string, a number, a boolean, an array, or an object—it cannot be a hybrid of a string and an object.
  4. Typeless Text Values: XML stores all tag values as raw character strings. If a converter naively infers numbers, values like the Boston ZIP code "02138" are coerced into the integer 2138, permanently destroying the leading zero.
  5. Namespaces and Prefixes: XML uses namespaces (xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/") to avoid tag collisions across distributed schemas. Converting without namespace awareness causes tag collisions or strips critical context.

How to convert XML to JSON step by step#

To protect the integrity of production data, follow a disciplined 5-step conversion pipeline.

Step 1: Validate XML syntax and structural well-formedness#

An XML parser cannot process malformed markup. If tags are unclosed, attribute quotes are mismatched, or illegal entity characters exist, parsing terminates immediately.

Before conversion, confirm that your XML is well-formed according to W3C standards: every opening tag must have a matching closing tag, and special characters must be properly escaped (&amp;, &lt;, &gt;).

Step 2: Establish an attribute preservation strategy#

To prevent attributes from disappearing or overwriting child tags with the same name, use a dedicated attribute prefix convention. The standard industry approach prefixes attribute names with an @ character:

XML
1<product sku="KBD-01" active="true">2  <name>Mechanical Keyboard</name>3</product>

When converted with attribute preservation:

JSON
1{2  "product": {3    "@sku": "KBD-01",4    "@active": "true",5    "name": "Mechanical Keyboard"6  }7}

This guarantees that an attribute named name="Widget" will never collide with a child element named <name>Widget</name>.

Step 3: Handle inner text with dedicated text node keys#

When an XML element contains both attributes and direct text content, map the text content to a reserved property name, typically #text:

XML
1<price currency="USD">.</price>

Converting this node yields a clean, unambiguous JSON object:

JSON
1{2  "price": {3    "@currency": "USD",4    "#text": "149.99"5  }6}

If an element has no attributes and only contains text (e.g., <name>Alice</name>), it simplifies directly to a standard string property: "name": "Alice".

Step 4: Configure type inference carefully#

Determine whether your application requires raw string retention or primitive type inference:

  • String retention (safest for scalar spelling): Values such as "@id": "0123" and "active": "true" remain strings. This preserves leading zeros and avoids numeric rounding, although whitespace trimming and XML-only constructs still require separate decisions.
  • Type Inference: Converts "true" to true and "42" to 42. Only enable this if your dataset does not contain telephone numbers, postal codes, or identifiers that look like numbers.

Step 5: Format and inspect the resulting JSON#

Once the JSON is produced, verify that all nested hierarchies, lists, and metadata tags are mapped properly. You can paste the output into ToolPorta's JSON Formatter to inspect the structure with clean indentation, or use the JSON Validator to ensure compliance with RFC 8259.

Realistic before-and-after conversion example#

Consider a realistic XML response from an enterprise warehouse logistics service, containing attributes, single and multiple sibling elements, leading zeros, and namespace declarations:

XML
1<?xml version="1.0" encoding="UTF-8"?>2<shipment id="SHP-88204" status="in_transit" xmlns:geo="https://standards.example.com/geo">3  <destination>4    <facilityCode></facilityCode>5    <address zipCode="02138">6      <city>Cambridge</city>7      <state>MA</state>8    </address>9    <geo:coordinates latitude="42.3736" longitude="-71.1097"/>10  </destination>11  <packages>12    <package trackingNumber="1Z999AA10123456784" fragile="true">13      <weight unit="kg">.</weight>14      <item>Ergonomic Monitor Stand</item>15      <item>Dual Display Arm Mount</item>16    </package>17  </packages>18  <specialNotes priority="high">Gate code: #4092. Deliver before 17:00.</specialNotes>19</shipment>

When converted with attributes preserved, namespace prefixes maintained, type inference disabled, and the default value trimming enabled, ToolPorta preserves the values and structures used in this example:

JSON
1{2  "shipment": {3    "@id": "SHP-88204",4    "@status": "in_transit",5    "@xmlns:geo": "https://standards.example.com/geo",6    "destination": {7      "facilityCode": "00482",8      "address": {9        "@zipCode": "02138",10        "city": "Cambridge",11        "state": "MA"12      },13      "geo:coordinates": {14        "@latitude": "42.3736",15        "@longitude": "-71.1097"16      }17    },18    "packages": {19      "package": {20        "@trackingNumber": "1Z999AA10123456784",21        "@fragile": "true",22        "weight": {23          "@unit": "kg",24          "#text": "4.25"25        },26        "item": ["Ergonomic Monitor Stand", "Dual Display Arm Mount"]27      }28    },29    "specialNotes": {30      "@priority": "high",31      "#text": "Gate code: #4092. Deliver before 17:00."32    }33  }34}

Notice that:

  • The leading zero in <facilityCode>00482</facilityCode> and the ZIP code "02138" are completely intact.
  • The weight element cleanly isolates both the @unit attribute and #text value "4.25".
  • The repeated <item> elements vectorized automatically into a standard JSON array.
  • The geo: namespace qualifier was preserved to avoid property name collisions.

Common pitfalls and edge cases that cause data loss#

Understanding the most common failure modes helps you prevent silent regressions in backend data pipelines.

1. The "Singleton Array" trap#

The most notorious bug in XML-to-JSON conversion is the singleton array problem.

When an XML list contains two or more sibling elements, parsers naturally create an array:

XML
1<!-- Two items: Parsed as an array -->2<tags>3  <tag>alpha</tag>4  <tag>beta</tag>5</tags>6<!-- Output: { "tags": { "tag": ["alpha", "beta"] } } -->

However, if an XML feed returns only one item:

XML
1<!-- One item: Naively parsed as a single string/object -->2<tags>3  <tag>alpha</tag>4</tags>5<!-- Naive Output: { "tags": { "tag": "alpha" } } -->

Downstream frontend code expecting an array will execute data.tags.tag.map(...) or data.tags.tag.forEach(...), triggering a fatal runtime error: TypeError: data.tags.tag.map is not a function.

The Fix: In production backend systems, use parsers with schema awareness or post-process the resulting JSON with a normalization helper that guarantees properties expected to be collections always evaluate to an array:

JavaScript
1// Defensive normalization helper2const tags = Array.isArray(data.tags.tag) ? data.tags.tag : [data.tags.tag];

For programmatic implementations, review our JavaScript XML to JSON guide or equivalent Python conversion examples.

2. Accidental type coercion and truncated identifiers#

Naive converters attempt to convert string values like "1234" or "007" into numeric primitives. This causes three types of irreversible data corruption:

  1. Stripped Leading Zeros: Account numbers, SWIFT codes, barcode numbers, and postal codes like "0042" become 42.
  2. 64-Bit Integer Truncation: Distributed database keys (such as Snowflake IDs like 1840829837492817283) exceed JavaScript's Number.MAX_SAFE_INTEGER (2^53 - 1) and lose their final digits when cast into floating-point numbers.
  3. Boolean Confusion: Certain codes like "N" or "0" may be misinterpreted as boolean false.

The Fix: Keep type inference disabled by default when ingesting third-party or financial XML feeds. Parse values as strings and selectively cast them only within your business logic.

3. Mixed content stripping#

In document-centric XML (such as DITA, DocBook, or XHTML), tags often embed inline markup within sentences:

XML
1<summary>System status is <status code="ok">Operational</status> as of today.</summary>

A naive parser will either discard the text "System status is" or flatten the entire element into a single string "System status is Operational as of today.", permanently deleting the <status> tag and its code="ok" attribute.

The Fix: If order within narrative mixed content matters, use an order-aware document parser or a representation that records every text and element node in sequence. CDATA alone does not solve the mapping problem; ToolPorta reads CDATA content as ordinary text and does not retain the CDATA boundary.

4. Stripping XML namespaces#

In enterprise web services (such as SOAP or XML Signature), identical tag names often exist across different namespaces:

XML
1<root xmlns:user="https://example.com/user" xmlns:billing="https://example.com/billing">2  <user:name>Alice</user:name>3  <billing:name>Corporate Account</billing:name>4</root>

If the converter strips namespace prefixes, both tags collide under the single key "name", causing one to overwrite the other. Always preserve namespace prefixes when converting enterprise or multi-vendor XML payloads.

XML vs JSON data model comparison#

FeatureXML (W3C Standard)JSON (RFC 8259)Lossless Conversion Strategy
Structural UnitsElements, attributes, text nodes, comments, processing instructionsObjects ({}) and arrays ([])Map attributes and text explicitly; decide whether XML-only nodes may be dropped
List RepresentationSibling tags with identical namesOrdered arrays wrapped in []Vectorize repeated tags; normalize singletons
Attributes SupportNative in opening tag (<tag k="v">)Not supported (keys are uniform)Use an attribute prefix to prevent property collisions
Data TypesEverything is character textString, Number, Boolean, Null, Array, ObjectKeep strings intact to prevent number truncation
Root RequirementStrictly one enclosing root tagAny single JSON valueThe XML root tag becomes the top-level JSON key
Mixed ContentPreserves ordered text and child nodesHas no standard XML node modelRequires an order-aware custom representation
Namespace SupportBuilt-in (xmlns:prefix)Unsupported nativelyRetain prefixes as part of the JSON property key
Document SizeOften has more markupOften has less markupCompare and compress representative payloads instead of assuming a fixed advantage

Frequently asked questions#

How do I handle XML elements that contain both attributes and text?#

The standard industry convention is to map attributes with an @ prefix (e.g., "@id": "42") and store the direct text content under a reserved key, typically "#text". For example, <amount currency="USD">99.50</amount> becomes {"amount": {"@currency": "USD", "#text": "99.50"}}. ToolPorta's converter applies this mapping automatically.

Why did my single-item XML list convert to an object instead of an array?#

In XML, there is no structural difference between an element that happens to be repeated and an element that appears only once. When a parser encounters a single <item> tag, it cannot know whether it is part of a list without a schema. To avoid breaking downstream code that calls .map(), check whether the property is an array and wrap it in [value] if it is not.

Should I enable automatic type inference when converting XML to JSON?#

Only enable type inference if your XML payload contains standard numbers and booleans that do not require leading zero preservation. If your dataset includes ZIP codes, phone numbers, UUIDs, or large 64-bit database identifiers, keep type inference disabled to prevent data corruption.

Converting XML documents into clean, idiomatic JSON simplifies application development and accelerates API integrations. ToolPorta provides a comprehensive suite of browser-based utilities to streamline your data pipelines: