quick-xml-to-json
High-performance XML to JSON converter built on top of quick-xml.
This crate provides a fast, memory-efficient way to convert XML documents to JSON format, leveraging quick-xml's high-performance XML parsing capabilities.
Features
- High Performance: Built on quick-xml for maximum parsing speed
- Zero-Copy Option:
xml_to_json_from_sliceborrows events directly from in-memory input - Streaming: Processes XML as a stream without building a DOM — peak heap stays ~2 MiB regardless of document size
- Attribute Support: Preserves XML attributes in JSON output
- Entity Handling: Resolves predefined entities and numeric character references
- Error Handling: Comprehensive error types with
thiserror
Installation
Add this to your Cargo.toml:
[]
= "0.3.0"
Usage
Three entry points, chosen by input shape:
xml_to_json_from_slice(&[u8], impl Write)— fastest; use when the document is already in memoryxml_to_json_from_bufread(impl BufRead, impl Write)— use when the input is already bufferedxml_to_json(impl Read, impl Write)— wraps any reader in aBufReaderfor you
Basic Conversion
use xml_to_json_from_slice;
let xml = r#"<users count="3">
<user age="40">Jane Doe</user>
<user age="42">John Doe</user>
</users>"#;
let mut output = Vecnew;
xml_to_json_from_slice?;
// output now contains the JSON bytes
let json_string = Stringfrom_utf8?;
println!;
JSON Output Format
The crate converts XML to JSON using a specific format that preserves structure:
- Attributes are prefixed with
@(e.g.,@id="value") - Text content is stored under the
#tkey - Child elements are stored under the
#ckey as an array
Example XML:
Hello World
Becomes:
Text, Entities, and Whitespace Semantics
- An element's character data is emitted as a single
#tvalue, concatenated in document order. For mixed content (<a>pre <b/>post</a>), text before and after child elements is joined into one#talongside#c. - Leading and trailing whitespace of an element's text is trimmed; interior whitespace (including newlines) is preserved exactly.
- Predefined entities (
&,<,>,",') and numeric character references (A,A) are resolved in both text and attribute values. Named entities that cannot be resolved (e.g. DTD-defined entities likeü) pass through verbatim rather than erroring or being dropped. Malformed character references (e.g.&#xZZ;) are errors. - CDATA sections are ignored.
Performance
XML fixture files have been sourced from https://aiweb.cs.washington.edu/research/projects/xmltk/xmldata/www/repository.html.
Measured with criterion on a 12-core MacBook Pro M4:
| Fixture | Size | xml_to_json_from_bufread |
xml_to_json_from_slice |
|---|---|---|---|
| SigmodRecord.xml | ~500 KB | 354 MiB/s | 405 MiB/s |
| mondial-3.0.xml | ~1.8 MB | 416 MiB/s | 429 MiB/s |
| orders.xml | ~5.4 MB | 393 MiB/s | 466 MiB/s |
| nasa.xml | ~25.1 MB | 414 MiB/s | 467 MiB/s |
On synthetic documents of many small elements, the buffered path sustains 9.2–9.5 million elements per second and the slice path 10.0–10.4 million.
Memory
Peak heap allocated during conversion, measured with a counting allocator while streaming each fixture from file to a sink: 2.01 MiB for SigmodRecord, mondial, and orders; 2.11 MiB for the ~25 MB nasa.xml. The footprint is dominated by the 2 MiB output buffer and is independent of document size. Reproduce with:
Run benchmarks with:
Test Suite
Run the test suite with:
Dependencies
- quick-xml: High-performance XML parsing
- simdutf8: SIMD-accelerated UTF-8 validation
- thiserror: Custom error types
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a pull request.