When integrating modern frontend applications with legacy systems, you inevitably hit a wall: your application speaks JSON, but the legacy API speaks XML.

Converting an XML payload to JSON seems simple until you encounter real-world enterprise XML—complete with nested namespaces, inline attributes, and deeply minified responses. Because XML and JSON use fundamentally different data models, a 1-to-1 automated translation often mangles the structure.

In this guide, we will explain how to safely convert XML payloads to predictable JSON locally, without leaking data to cloud servers.

The Pre-Flight Check: Validating Malformed XML

Legacy API responses are rarely perfect. Before passing a payload into an automated conversion script, you must ensure it is valid XML. A single unclosed tag or stray CDATA block can cause parsers to throw cryptic errors (or worse, like fast-xml-parser, they may silently accept the malformed XML and output corrupted JSON).

Instead of guessing, use xmllint to validate the file locally (built-in on macOS; install libxml2-utils on Linux):

# Validate the XML without outputting the full text
xmllint --noout legacy-payload.xml

If the XML is malformed, xmllint will output the exact line and column number. If you need to visually inspect the structure without breaking CDATA tags, format it safely:

xmllint --format legacy-payload.xml > formatted.xml

How to Convert XML to JSON Locally

Never paste raw API payloads containing PII, internal IDs, or API keys into generic “Free Online XML to JSON Converters.” Many of these utilities are server-rendered and transmit your entire payload to an external server, creating a security vulnerability.

Instead, perform the conversion locally using open-source CLI tools or Utiliome’s 100% client-side XML to JSON Converter.

Method 1: The CLI Swiss Army Knife (yq)

If you are comfortable in the terminal, yq (Mike Farah’s Go-based port of jq) is an excellent way to translate XML payloads locally.

Install yq via Homebrew:

brew install yq

Run the conversion, specifying XML as the input format (-p) and JSON as the output format (-o):

yq -p xml -o json legacy-payload.xml > clean-payload.json

Note: As of yq v4.30.1, XML attributes are prefixed with +@ in the resulting JSON (e.g., <user id="1"> becomes "+@id": "1").

Method 2: Python and xmltodict

For complex enterprise integrations, you often need programmatic control to strip out SOAP envelopes or force specific nodes to remain lists. Python’s xmltodict library is the industry standard.

Install the library using your virtual environment:

python3 -m pip install xmltodict

Write a localized conversion script:

import xmltodict
import json

with open("legacy-payload.xml", "r") as xml_file:
    xml_content = xml_file.read()

# Parse XML into a Python Dictionary
# - process_namespaces: Strips namespaces or maps them
# - force_list: Forces the 'user' node to always be a list, even if there's only one
data_dict = xmltodict.parse(
    xml_content, 
    process_namespaces=True, 
    # Note: Any unmapped namespace URI will become part of the JSON key!
    # To strip all, you must map every URI to None in this dictionary.
    namespaces={'http://schemas.xmlsoap.org/soap/envelope/': None},
    force_list=('user',)
)

with open("clean-payload.json", "w") as json_file:
    json.dump(data_dict, json_file, indent=2)

Method 3: Browser-Based Local Conversion

If you don’t want to write a script, you can use Utiliome’s XML to JSON Converter. It uses fast-xml-parser running natively in your browser’s JavaScript engine. It supports toggling attribute preservation and forcing array outputs, and your API payloads never leave your machine.

The 3 Major XML-to-JSON Gotchas

When transitioning from XML to JSON, watch out for these three architectural mismatches:

1. The Single-Item Array Trap

Consider this XML:

<users>
  <user>Alice</user>
</users>

Most generic parsers convert this into an object: {"users": {"user": "Alice"}}. But if the API returns two users tomorrow, it becomes an array: {"users": {"user": ["Alice", "Bob"]}}. Your downstream application will crash because it occasionally receives an object instead of an array.

Always configure your parser to strictly enforce list types. For fast-xml-parser, use isArray: (name) => name === 'user'. Note: fast-xml-parser silently converts types (like dropping leading zeros) unless you set parseTagValue: false.

2. The Attribute Dilemma

XML supports both text nodes and attributes:

<user id="992" active="true">John Doe</user>

JSON only has key-value pairs. Parsers solve this by converting attributes into objects with special prefixes, and moving the text node to a #text key.

  • xmltodict: @id and #text
  • fast-xml-parser: @_id and #text
  • yq: +@id and +content

3. Namespace Clutter

Enterprise SOAP responses are buried in namespaces (<soapenv:Envelope>). Left unchecked, your JSON keys will look like "soapenv:Envelope", forcing developers to write brittle extraction logic. Use your local script (like xmltodict’s process_namespaces) to strip them during conversion.