Jsonify — XML to JSON¶
pygixml.jsonify serializes XML directly to JSON. “Directly” is the
operative word: the in-memory entry points (dumps()
and friends) traverse the pugixml DOM in C++ and write straight into a
JSON string buffer — no intermediate Python dict/list is ever
built, unlike going through pygixml.dictify.parse() followed by
json.dumps(). The streaming entry points
(stream_dump(),
stream_jsonl()) go a step further and skip
even the DOM, converting a giant XML file to a giant JSON file in
roughly constant memory.
The output shape matches pygixml.dictify.parse() exactly (same
@-prefixed attributes, #text for mixed content, repeated
siblings collapsed into arrays) — jsonify.dumps(xml) is equivalent
to, but faster than, json.dumps(dictify.parse(xml)).
from pygixml import jsonify
xml = """
<database name="users_db">
<user id="101">
<name>Mohammad</name>
<balance>450.75</balance>
</user>
<tag>active</tag>
<tag>verified</tag>
</database>
"""
jsonify.dumps(xml)
# '{"database": {"@name": "users_db", "user": {"@id": "101", ...}, "tag": ["active", "verified"]}}'
jsonify.dumps(xml, pretty=True, indent=" ") # multi-line, 2-space indent
In-memory entry points¶
- pygixml.jsonify.dumps(source, attr_prefix='@', cdata_key='#text', force_list=None, pretty=False, indent='\\t', encoding='utf-8')
Smart dispatcher — serializes XML to JSON regardless of what form the XML is already in:
strstarting with<→ parsed and serialized (same asdumps_str())ObjectifiedElement→ serialized directly from the live DOM subtree (same asdumps_obj())XMLNode→ serialized directly (same asdumps_node())
- Parameters:
source – XML string, an already-parsed
ObjectifiedElement, or anXMLNode.attr_prefix (str) – Prefix prepended to attribute keys. Default
"@".cdata_key (str) – Key used for text content in mixed nodes. Default
"#text".force_list (set or True or None) – Tag names that should always be wrapped in a JSON array, even when only one sibling exists. Pass
Trueto force every tag.pretty (bool) – Indent the output. Default
False(compact).indent (str) – Indentation string used when
pretty=True. Default a tab.
- Returns:
JSON string.
- Return type:
str
- Raises:
PygiXMLError – If the XML is malformed.
TypeError – If
source’s type isn’t recognized.ValueError – If
sourceis astrthat doesn’t look like XML (file paths are rejected here on purpose — usedumps_file()explicitly for files).
Note
File input is intentionally excluded from the dispatcher — call
dumps_file()directly for a path, so it’s always unambiguous whether astrargument is XML content or a file path.
- pygixml.jsonify.dumps_str(xml, attr_prefix='@', cdata_key='#text', force_list=None, pretty=False, indent='\\t', encoding='utf-8')
Parse an XML string and serialize it directly to JSON.
- Parameters:
xml (str) – XML source text.
- Returns:
JSON string.
- Return type:
str
- Raises:
PygiXMLError – If the XML is malformed.
- pygixml.jsonify.dumps_file(path, attr_prefix='@', cdata_key='#text', force_list=None, pretty=False, indent='\\t', encoding='utf-8')
Parse an XML file and serialize it directly to JSON, returning the result as a
str. For files too big to hold as a JSON string in memory, seestream_dump()instead.- Parameters:
path (str) – Filesystem path to the XML file.
- Returns:
JSON string.
- Return type:
str
- Raises:
PygiXMLError – If the file cannot be read or the XML is malformed.
- pygixml.jsonify.dumps_obj(elem, attr_prefix='@', cdata_key='#text', force_list=None, pretty=False, indent='\\t', encoding='utf-8')
Serialize an already-parsed
ObjectifiedElementsubtree directly to JSON, without re-parsing or re-traversing as a dict first.- Parameters:
elem (pygixml.ObjectifiedElement) – Element to serialize.
- Returns:
JSON string.
- Return type:
str
- Raises:
TypeError – If
elemis not anObjectifiedElement.
from pygixml import objectify, jsonify root = objectify.from_string(xml) jsonify.dumps_obj(root.user) # just the <user> subtree
- pygixml.jsonify.dumps_node(node, attr_prefix='@', cdata_key='#text', force_list=None, pretty=False, indent='\\t', encoding='utf-8')
Serialize a low-level
XMLNodedirectly to JSON.- Parameters:
node (pygixml.XMLNode) – Node to serialize.
- Returns:
JSON string.
- Return type:
str
- Raises:
TypeError – If
nodeis not anXMLNode.
Streaming entry points: constant memory, files in and out¶
The functions above all hold the result (and, except for
dumps_obj/dumps_node, the parsed DOM too) in memory — fine for
documents that fit comfortably in RAM. For documents that don’t,
jsonify has two streaming converters that go file-to-file, entirely
in C++, with no pugixml DOM, no Python dict/list/str for
individual elements, and no json module anywhere in the call chain.
- pygixml.jsonify.stream_dump(xml_path, json_path, attr_prefix='@', cdata_key='#text', force_list=None, indent=0, stack_size=4096, io_buf_size=65536)
Convert a (potentially gigantic) XML file into a single, standard, valid JSON file — in roughly constant memory. Produces exactly what
dumps_file()would produce (one JSON value mirroring the whole document, loadable with a plainjson.load), just without ever holding the document, or the output, fully in memory.- Parameters:
xml_path (str) – Path to the input XML file.
json_path (str) – Path to the output JSON file. Overwritten if it exists.
attr_prefix (str) – Prefix for XML attribute names in JSON keys. Default
"@".cdata_key (str) – JSON key used for text content mixed with attributes or child elements. Default
"#text".force_list (set or True or None) – Tag names always serialized as a JSON array.
Trueforces every tag. DefaultNone(a tag becomes an array only once a second sibling with that name actually appears).indent (int) – Spaces per nesting level, same convention as
json.dump(..., indent=N).0(default) is compact; any positive value pretty-prints.stack_size (int) – Size in bytes of yxml’s internal name stack.
io_buf_size (int) – Bytes read per XML I/O operation. Default 64 KB.
- Returns:
Number of XML elements processed (informational).
- Return type:
int
- Raises:
PygiXMLError – On malformed XML, or if the input/output file cannot be opened.
How it stays constant-memory while still producing valid JSON syntax. A JSON array needs to know, before its closing
], whether more items follow — but the parser only finds that out when (and if) a second same-tag sibling actually shows up. Rather than buffer whole subtrees to be safe, the engine writes optimistically and patches the output file in place once it learns more:The first time a child tag is seen under some parent, one placeholder byte is reserved right before its value, and the tag is written as a plain (non-array) value.
A second sibling with the same tag arrives → that placeholder byte is overwritten with
[(an O(1) patch), and the new value is appended right after the first. This is the common case for record-oriented XML (same-tag siblings adjacent in the source) and never moves a single byte.A different child tag is interleaved between two same-tag siblings → the engine splices: it shifts just the interleaved bytes forward (in small fixed-size chunks) to open a gap for the new sibling. Cost is proportional to how much was interleaved, not to the file size — and it’s the only case where any data movement happens at all.
from pygixml import jsonify import json jsonify.stream_dump("huge.xml", "huge.json") # compact jsonify.stream_dump("huge.xml", "huge.json", indent=2) # pretty with open("huge.json") as f: data = json.load(f) # a single, ordinary, valid JSON document
- pygixml.jsonify.stream_jsonl(xml_path, jsonl_path, tag, attr_prefix='@', cdata_key='#text', force_list=None, stack_size=4096, io_buf_size=65536)
The file-to-file counterpart of
iterjsonl()(and the per-tag, filtering complement tostream_dump(), which always converts the entire document): streams an XML file straight to a.jsonlfile, one matched element per line, entirely in C++. Unlikeiterjsonl(), noStreamElementand no Pythonstr/dict/listis ever created for the matched elements themselves — each element’s JSON object is assembled in a small in-memory buffer (bounded by that one element’s own subtree, the same constant-memory model asiterfind()) and written straight to the file.- Parameters:
xml_path (str) – Path to the source XML file.
jsonl_path (str) – Path to the
.jsonlfile to write. Overwritten if it exists.tag (str) – Tag name of the elements to convert and write, one per line.
attr_prefix – Same meaning as
stream_dump().cdata_key – Same meaning as
stream_dump().force_list – Same meaning as
stream_dump().stack_size (int) – Size in bytes of yxml’s internal name stack.
io_buf_size (int) – Bytes read per XML I/O operation. Default 64 KB.
- Returns:
Number of matched elements written.
- Return type:
int
- Raises:
PygiXMLError – On malformed XML, or if the input/output file cannot be opened.
Note
If
tagappears nested inside an already-matched element, that inner occurrence is folded into the outer match as an ordinary nested field (under its own tag-name key) rather than written as a second, separate line — only the outermost occurrence of a match starts a new JSONL record. This only matters for genuinely self-nested tags; a flat list of repeated sibling records (the common case) is unaffected.from pygixml import jsonify n = jsonify.stream_jsonl("huge.xml", "huge.jsonl", "record") print(f"wrote {n} records") import json with open("huge.jsonl") as f: for line in f: record = json.loads(line) # each line is independent, valid JSON
Choosing the right entry point¶
You have / want |
In memory |
Streamed (constant memory) |
|---|---|---|
Whole document → one JSON value |
||
One record per line, output as a |
Loop + write yourself (see
|
|
One record per line, kept as Python |
(inherently produces a Python object per line — see Streaming — Constant-Memory Parsing for Big XML for the all-C++ alternative when that’s not needed) |
|
Already-parsed |
(n/a — already in memory) |
See Streaming — Constant-Memory Parsing for Big XML for iterjsonl and the rest of the underlying
constant-memory parsing layer that stream_dump and
stream_jsonl are built on top of.