A high-performance XML-to-JSON converter written in Rust with Python bindings. It uses XSD schema definitions to produce correctly typed JSON output — integers, floats, booleans, and arrays are represented as their proper JSON types rather than treating everything as strings.
The primary focus for now is DATEX II data, but the library should be generic enough to support any XML-based data format.
This library is mostly AI-generated and not fully reviewed, as I lack Rust skills. It's mostly tested against highly complex DATEX II schemas, and the results are convincing: it can handle the DATEX II schema variety, the outputs are correct, and it's fast enough to handle the enormous DATEX II XMLs. In both aspects, correctness and performance, it's better than any other library I found, even outside the Python ecosystem. Still, depending on your development preferences, you might not want to use this library.
- Schema-aware conversion: Uses XSD schemas to determine JSON types (string, integer, float, boolean) and array structures (via
maxOccurs) - Correct array handling: Elements with
maxOccurs="unbounded"are always emitted as JSON arrays, even when only a single element is present - Selectable JSON mapping conventions: pick the output shape that fits your downstream consumer
- BadgerFish (default): attributes prefixed with
@, mixed-content text under$ - Parker: attributes dropped, simple elements collapse to their value, root element name stripped
- Abdera: attributes nested under
attributes, children nested underchildren
- BadgerFish (default): attributes prefixed with
- Streaming file I/O: Converts large XML files with buffered reading/writing (8 MB buffers)
- Pre-parsed schemas: Parse an XSD once, reuse it across multiple conversions
- XSD import/include support: Automatically resolves and parses referenced schema files
xsi:typesupport: Overrides element types dynamically based onxsi:typeattributes- Python bindings via PyO3: Use directly from Python as a native module
- Rust >= 1.85 (edition 2024)
- Python >= 3.8
- maturin (for building the Python package)
- uv (optional, for managing the Python environment)
# Using maturin directly
maturin develop --release
# Or using uv
uv pip install -e .This compiles the Rust code and installs the rs_xml2json Python module.
A Docker-based build produces a self-contained .whl file for your local architecture without needing Rust or maturin installed on the host. By default it matches your system Python version (e.g. 3.12, 3.13, 3.14).
Two build variants are available:
| Variant | Dockerfile | Wheel type | Use case |
|---|---|---|---|
debian (default) |
Dockerfile.debian |
manylinux |
Standard glibc-based distros (Ubuntu, Debian, Fedora, ...) |
alpine |
Dockerfile.alpine |
musllinux |
Alpine-based / musl environments |
# Build a manylinux wheel matching your system Python version (output goes to dist/)
make build
# Build a musllinux wheel instead
make build VARIANT=alpine
# Build for a specific Python version
make build PYTHON_VERSION=3.13
make build PYTHON_VERSION=3.14
# Combine both options
make build VARIANT=alpine PYTHON_VERSION=3.14The resulting wheel will be in the dist/ directory. To build and install into a local .venv in one step:
make installThis creates the venv if it doesn't exist, builds the wheel, and installs it.
cargo build --releasefrom rs_xml2json import Convention, Schema, convert, convert_to_file, convert_bytes, \
convert_with_schema, convert_to_file_with_schema, convert_bytes_with_schema# Convert XML file to JSON string (default convention: BadgerFish)
json_string = convert("data.xml", "schema.xsd")
# Pick a different convention
json_string = convert("data.xml", "schema.xsd", Convention.PARKER)
json_string = convert("data.xml", "schema.xsd", Convention.ABDERA)
# Convert XML file to JSON file (streaming, low memory)
convert_to_file("data.xml", "schema.xsd", "output.json")
# Convert raw bytes
json_string = convert_bytes(xml_bytes, xsd_bytes)from rs_xml2json import Schema
# Parse the schema once
schema = Schema.from_file("schema.xsd")
# Or from bytes
schema = Schema.from_bytes(xsd_bytes)
# Reuse across conversions (convention argument is optional)
json_string = convert_with_schema("data.xml", schema)
convert_to_file_with_schema("data.xml", schema, "output.json", Convention.PARKER)
json_string = convert_bytes_with_schema(xml_bytes, schema)# Default (BadgerFish) — prints JSON to stdout
rs-xml2json data.xml schema.xsd
# Write to a file
rs-xml2json data.xml schema.xsd output.json
# Choose a convention
rs-xml2json data.xml schema.xsd --convention parker
rs-xml2json data.xml schema.xsd output.json --convention abderaGiven this XSD schema:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="catalog" type="CatalogType"/>
<xs:complexType name="CatalogType">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="item" type="ItemType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="version" type="xs:integer"/>
</xs:complexType>
<xs:complexType name="ItemType">
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="price" type="xs:decimal"/>
<xs:element name="quantity" type="xs:integer"/>
<xs:element name="available" type="xs:boolean"/>
</xs:sequence>
<xs:attribute name="id" type="xs:integer"/>
</xs:complexType>
</xs:schema>And this XML:
<catalog version="2">
<name>Test Catalog</name>
<item id="1">
<title>Widget</title>
<price>9.99</price>
<quantity>100</quantity>
<available>true</available>
</item>
</catalog>The output JSON will be:
{
"catalog": {
"@version": 2,
"name": "Test Catalog",
"item": [
{
"@id": 1,
"title": "Widget",
"price": 9.99,
"quantity": 100,
"available": true
}
]
}
}Note that item is always an array (because the schema declares maxOccurs="unbounded"), and numeric/boolean values are properly typed.
rs-xml2json supports three named conventions. The default is BadgerFish. Schema-driven type coercion (XSD types → JSON scalars) and array detection (maxOccurs > 1 → always an array, even for a single item) apply to all three.
| XML construct | BadgerFish (default) | Parker | Abdera |
|---|---|---|---|
| Element with simple type | Value (string, number, boolean) | Value | Value |
| Element with complex type | Object | Object | Object with optional attributes and children |
Element with maxOccurs > 1 |
Array | Array | Array |
| Attribute | Key prefixed with @ |
Dropped | Nested under attributes |
| Mixed content text | $ key |
Dropped (Parker doesn't handle mixed) | Stored under children when no child elements |
| Empty element | null |
null |
null |
| Root element name | Preserved as outer key | Stripped (the root body is the top-level JSON) | Preserved as outer key |
For the XML shown above (<catalog version="2"> ... </catalog>):
BadgerFish (default):
{ "catalog": { "@version": 2, "name": "Test Catalog", "item": [ { "@id": 1, "title": "Widget", "price": 9.99, "quantity": 100, "available": true } ] } }Parker (no attributes, root name dropped):
{ "name": "Test Catalog", "item": [ { "title": "Widget", "price": 9.99, "quantity": 100, "available": true } ] }Abdera (attributes/children separated):
{ "catalog": { "attributes": { "version": 2 }, "children": { "name": "Test Catalog", "item": [ { "attributes": { "id": 1 }, "children": { "title": "Widget", "price": 9.99, "quantity": 100, "available": true } } ] } } }# Rust tests
cargo test
# Python CLI tests (requires the module to be installed first)
pip install -e ".[dev]"
maturin develop
pytest tests/test_cli.py -v├── Cargo.toml # Rust package manifest
├── pyproject.toml # Python package manifest (maturin)
├── src/
│ ├── lib.rs # Python bindings (PyO3 module)
│ ├── error.rs # Error types
│ ├── schema/
│ │ ├── mod.rs
│ │ ├── model.rs # Schema data model (ElementDef, TypeDef, etc.)
│ │ ├── parser.rs # XSD parser
│ │ └── type_map.rs # XSD built-in type → JSON type mapping
│ └── converter/
│ ├── mod.rs
│ ├── convention.rs # Convention enum (BadgerFish/Parker/Abdera)
│ └── walker.rs # XML → JSON conversion engine
├── python/
│ └── rs_xml2json/
│ ├── __init__.py # Python package re-exports
│ └── cli.py # `rs-xml2json` command-line entry point
├── tests/
│ ├── integration_test.rs # Rust integration tests
│ ├── test_cli.py # Python CLI integration tests (pytest)
│ ├── sample.xsd # Test schema
│ └── sample.xml # Test data
└── data/
└── schema.xsd # Example schema
See the project license file for details.