Converting XML Into JSON for Python Data Workflows
Your Python script expects a list of records. The file you received contains XML tags, attributes, and repeated elements instead. Converting that file to JSON can make the next step easier, but the conversion also makes decisions about your data. An attribute needs a home. A repeated element needs an array. An empty tag needs a meaning. Understanding those decisions before you process the result helps prevent missing values, broken loops, and silent changes to identifiers.
Start With the Mapping: XML and JSON organize information differently. Decide how elements, attributes, repeated tags, empty values, and namespaces should appear before converting a file. Keep identifiers as strings, check array shapes, and preserve any meaningful text order. In Python, validate the parsed structure before calculations or data analysis. Keep the original XML available, and use a small sample to confirm that your conversion rules preserve the information your workflow needs.
XML Structure Needs a Deliberate JSON Mapping
XML describes a document through elements. Elements can contain text, attributes, and child elements. JSON represents values through objects, arrays, strings, numbers, booleans, and null. Those building blocks overlap, but they do not create a single universal conversion rule.
Consider a product record. Its identifier appears as an attribute, while its name and price appear as child elements:
<product id="007">
<name>Desk Lamp</name>
<price currency="USD">29.90</price>
</product>
One possible JSON representation keeps the root element, prefixes attribute names with an at sign, and stores text under a dedicated key when an element also has attributes:
{
"product": {
"@id": "007",
"name": "Desk Lamp",
"price": {
"@currency": "USD",
"#text": "29.90"
}
}
}
The keys @id and #text are conventions chosen by the conversion process. They are not special JSON syntax. Another converter might use separate attribute objects or different text keys. Your Python code must follow the actual mapping used in its input.
How Common XML Features Can Appear in JSON
| XML Feature | Possible JSON Mapping | What to Check |
|---|---|---|
Text element: <name>Lamp</name> |
"name": "Lamp" |
Whether surrounding whitespace is meaningful. |
Attribute: id="007" |
"@id": "007" |
Whether attributes remain separate from child elements. |
| Repeated sibling elements | An array under the element name | Whether a single occurrence also becomes an array. |
Empty element: <note/> |
An empty string, null, or an empty object | Whether empty and absent values remain distinguishable. |
| Text with child elements | An ordered sequence of text and element records | Whether text order survives conversion. |
Keep Attributes Separate From Element Values
Attributes often hold metadata about an element. A measurement might have a unit attribute. A record might have a source identifier. Flattening everything into the same object without a naming rule can create collisions.
For example, <item code="A1"><code>B2</code></item> contains both an attribute and a child element named code. A mapping that gives both values the same JSON key cannot represent them reliably. A prefix convention produces distinct keys: "@code" and "code".
Text also needs its own place when an element has attributes. In the product example, the price is not merely a string. Its JSON object carries both the currency and the amount. Reading that price in Python requires accessing product["price"]["#text"].
Choose these rules before writing downstream code. A documented mapping gives everyone working with the file the same expectations.
Make Repeated Elements Predictable for Python
Repeated XML elements usually map naturally to JSON arrays. Two <product> elements become two objects inside a "product" array. Trouble starts when a different file contains only one product.
Some conversion approaches produce an object for one occurrence and an array for several. That variability can break a loop that worked during testing. Define known collections as arrays even when they contain one record or no records.
Keep the root wrapper decision consistent too. A catalog might map to {"catalog": {"product": [...]}}. Removing the wrapper changes the access path. Neither shape is automatically correct, but switching between them creates avoidable work.
For an occasional file, an XML to JSON converter can help you examine a candidate output. Compare its treatment of attributes, text, and repeated tags against your intended structure before connecting that output to a Python workflow.
Preserve Meaning Before Converting Value Types
XML text does not automatically tell you which Python type a value should become. The characters 007 could represent an identifier. Turning them into the JSON number 7 removes leading zeros and changes their meaning.
Keep text as strings during the structural conversion unless an explicit schema or field rule says otherwise. Convert selected fields afterward. Prices, quantities, dates, and flags each need their own rules.
For money, Python’s Decimal can read a decimal string directly. For identifiers, retaining the string is usually the safer choice. A field containing "false" also needs explicit handling: calling bool("false") returns True because the string is nonempty.
Empty values deserve the same care. An absent note element, an empty <note/> element, and a note containing spaces may carry different meanings. Decide whether your application distinguishes them before trimming whitespace or replacing values with null.
Handle Namespaces and Document Order With Care
XML namespaces distinguish names that might otherwise look identical. Two elements with the local name id can belong to different namespace URIs. Removing namespace information can merge fields that should remain separate.
A robust mapping can retain expanded names containing the namespace URI or use a documented namespace mapping. Prefixes alone need care because different XML documents can use different prefixes for the same URI. Match names by their namespace identity when that distinction affects your data.
Mixed content creates another preservation issue. In <p>Read <em>this</em> carefully.</p>, text appears before and after a child element. A plain object with separate text and element keys may lose that sequence.
If you need to reconstruct document content, use an ordered array that records text and elements in sequence. If your workflow only needs the full sentence, extracting combined text may be enough. Make that choice explicit. JSON object member order should not serve as your document-order contract.
Build a Small Local Conversion With Explicit Rules
Python’s standard library provides XML parsing and JSON serialization. The following example implements a limited mapping for record-oriented XML. It preserves attributes with @, uses #text when needed, and forces product elements into arrays.
This example assumes trusted local input. It rejects mixed content because its object mapping cannot preserve that content’s order. It also does not preserve comments, processing instructions, or the original XML formatting.
import json
import xml.etree.ElementTree as ET
FORCE_ARRAY = {"product"}
def element_value(element):
children = list(element)
attributes = {
f"@{name}": value
for name, value in element.attrib.items()
}
if not children:
text = element.text or ""
if attributes:
attributes["#text"] = text
return attributes
return text
if (element.text or "").strip() or any(
(child.tail or "").strip() for child in children
):
raise ValueError("Mixed content needs an ordered mapping")
result = dict(attributes)
groups = {}
for child in children:
groups.setdefault(child.tag, []).append(
element_value(child)
)
for name, values in groups.items():
result[name] = (
values
if name in FORCE_ARRAY or len(values) > 1
else values[0]
)
return result
root = ET.parse("catalog.xml").getroot()
data = {root.tag: element_value(root)}
with open("catalog.json", "w", encoding="utf-8") as output:
json.dump(
data,
output,
ensure_ascii=False,
indent=2,
allow_nan=False,
)
The parser reads the XML file directly, allowing it to interpret the file’s encoding declaration. The output is written as UTF-8. With ensure_ascii=False, readable Unicode characters remain visible in the saved JSON.
For namespaced input, this parser represents element names using expanded names. Collection rules in FORCE_ARRAY must match those names. Also note that an absent collection stays absent in this example. Add an explicit normalization step if your application requires an empty array for that case.
Validate the Output Before Doing Data Work
A file that parses successfully is not necessarily correct for your application. JSON syntax validation catches broken formatting. Structural validation checks whether required fields, arrays, and value types meet your expectations. Business checks confirm whether the values make sense.
Start by reopening the saved JSON. This checks the artifact your next script will actually read. Then inspect required paths and validate records before converting amounts:
import json
from decimal import Decimal, InvalidOperation
with open("catalog.json", encoding="utf-8") as source:
saved = json.load(source)
products = saved["catalog"].get("product", [])
if not isinstance(products, list):
raise TypeError("Expected a product array")
for product in products:
identifier = product["@id"]
if not isinstance(identifier, str) or not identifier:
raise ValueError("Product identifier must be a string")
price = product["price"]
if price["@currency"] != "USD":
raise ValueError("Unexpected currency")
try:
amount = Decimal(price["#text"])
except InvalidOperation as error:
raise ValueError("Invalid price") from error
if not amount.is_finite() or amount < 0:
raise ValueError("Price must be finite and nonnegative")
Compare source and output record counts too. Check a few identifiers character for character. Include examples with accents, empty fields, one record, and several records. These checks catch conversion problems that a successful json.load() cannot detect.
If a receiving system applies a JSON Schema, validate against that contract before delivery. Confirm whether it expects wrapper objects, attribute prefixes, or flattened fields. Compatibility depends on the agreed structure, not just the file extension.
Keep Sensitive Files Within an Appropriate Boundary
Before using a browser-based service, check whether your file contains customer details, credentials, internal identifiers, or confidential records. Verify how the service processes and retains input. A browser interface alone does not establish that processing stays on your device.
Use synthetic sample data when evaluating an unfamiliar conversion workflow. For sensitive files, a local process can keep conversion within your existing environment. That process still needs sensible file permissions and careful logging. Avoid printing entire records into shared logs when a record count or field name would explain the problem.
Untrusted XML also needs a separate security decision. Do not treat the small parser example as a general upload handler. Use a parser configuration appropriate for hostile input, restrict resource use, and enforce input-size limits before accepting arbitrary files.
Give Your Python Code a Stable Starting Point
The most useful conversion result is predictable. Your code should know where attributes live, which fields are arrays, and when a value remains a string. Those decisions make later filtering, calculations, and data-frame creation easier to reason about.
Keep the original XML alongside your mapping rules when preservation matters. Ordinary XML-to-JSON conversion may lose formatting, comments, namespace declarations, or content order, so it should not replace the source archive. Once the structure and values pass your checks, the JSON file becomes a dependable starting point for the rest of your Python workflow.