xsd-tools: marshalling code in five languages from one XSD

June 2026 · QVXLabs · github.com/QVXLabs/xsd-tools

If you work with XML or JSON against an XSD schema, you've almost certainly hand-written the boring half: the code that walks a document into structs and back out again. It's tedious, it's repetitive, and it's exactly the kind of code that grows subtle bugs — an attribute read into the wrong field, a bound that's checked in the spec but not in the parser.

The generators that exist to spare you this are each welded to one language — JAXB for Java, xsd.exe for .NET, generateDS for Python. So the moment a schema is shared across services written in different languages, you're back to maintaining N separate, slowly drifting hand-rolled implementations of the same contract.

xsd-tools takes a different cut at it: one schema in, idiomatic marshalling code out, for ten targets across five languages — and it's open source. Today it ships its 0.2.0 release.

One schema, every binding

xsd-tools reads an XSD and runs it through a template that emits the target-language code. The built-in targets cover the C / C++ / Python / Java / TypeScript grid across both XML and JSON:

LanguageXMLJSON
Cexpat (SAX + DOM)json-c
C++11expatjson-c
Pythonstdlib xml.saxstdlib json
JavaJDK StAXorg.json
TypeScriptfast-xml-parser

Every one of those targets is round-trip tested in CI — generate the code, compile it, marshal a document, unmarshal it, and check it survives — on Linux, macOS, and Windows, across x86-64 and arm64. Change the schema once and regenerate, and the bindings stay in sync by construction.

The validation is generated too

This is the part that pays for itself. xsd-tools doesn't just map elements to fields — it carries the schema's constraints into the generated code. Take a rating bounded to 0–5:

<element name="rating">
  <simpleType>
    <restriction base="int">
      <minInclusive value="0"/>
      <maxInclusive value="5"/>
    </restriction>
  </simpleType>
</element>

The C/JSON target narrows that field to the smallest type that fits and emits the bound as a parse-time check — an out-of-range document is rejected, not silently accepted:

typedef struct {
    uint8_t  Content_;          /* 0..5 narrows to uint8_t */
} json_rating;

/* in the generated unmarshaller: */
if (!((pObj->Content_) >= 0 && (pObj->Content_) <= 5))
    /* facet violation — reject the document */

The same restriction becomes a Short with a range guard in Java, and the equivalent narrowed type and check in every other target. Range, length, and enumeration facets — on elements and attributes alike — are all enforced in the code the tool writes for you. xs:documentation rides along too, emitted as a comment at the matching type, field, or attribute.

What makes it different

Honest caveats

This is an early (0.2.0) release. It's round-trip tested across a wide grid, but it's young — expect rough edges and a moving API.

Getting it

The 0.2.0 release attaches prebuilt Linux packages — a Debian .deb, an RPM .rpm, and a single-file Flatpak bundle — and builds from source via CMake on Linux, macOS, and Windows. Point it at a schema and a target and the code prints to stdout:

xsdb python-sax library.xsd            # generated Python to stdout
xsdb --out-dir out c-json-jsonc library.xsd   # write the C/JSON files
xsdb --list                            # show the available targets

xsd-tools on GitHub 0.2.0 release

Open source and actively developed — issues and PRs welcome.