json-schema-rs
A Rust library for JSON Schema tooling: Schema→Rust codegen (generate Rust
types from a JSON Schema), Rust→Schema reverse codegen, and a validator.
The repo provides the json-schema-rs library and the jsonschemars CLI. We
target JSON Schema Draft 2020-12; default settings (e.g.
JsonSchemaSettings::default()) are tuned for that spec. A script downloads
specs for every published draft (draft-00 through 2020-12); run
make vendor_specs to fetch them locally—specs are not stored in the repo.
Supported keywords include type (object, string, integer, number, boolean), properties, required, const (draft-06+;
validator: instance must equal const; codegen: string const → single-value enum, non-string const → fallback; reverse: single-variant unit enum → const), enum
(string-only; codegen emits Rust enums), items (array with single-schema
items; codegen emits Vec<T> or Option<Vec<T>>; uniqueItems: when true,
codegen emits HashSet<T> for hashable item types and the validator enforces
uniqueness), minItems and maxItems (array/set length constraints;
validator enforces; codegen emits
#[json_schema(min_items = ..., max_items = ...)] on generated array/set
fields; reverse codegen supports the same attributes on Vec and HashSet fields),
minimum and maximum (validation and codegen type selection: narrow
integer/float types when both bounds are present and valid), minLength and
maxLength (string length constraints in Unicode code points; validator
enforces; codegen emits #[json_schema(min_length = ..., max_length = ...)]
on generated string fields; reverse codegen supports the same attributes on
String fields), pattern (string constraint as ECMA 262 regex; validator
enforces via regress; codegen emits #[json_schema(pattern = "...")];
reverse codegen supports the same attribute on String fields), default (meta-data only; codegen emits #[serde(default)] or #[serde(default = "fn")] so missing keys get the schema default; reverse codegen via #[json_schema(default = ...)]), description (codegen emits Rust doc comments; reverse
codegen via #[json_schema(description = "...")]),
examples (draft-06+; meta-data only; stored and round-tripped; codegen emits examples in doc comments for structs and enums; not used for validation; Full dedupe includes in key, Functional excludes), deprecated (draft 2019-09+; meta-data only; codegen emits #[deprecated] on the corresponding field or struct; reverse codegen via #[json_schema(deprecated = true)]), $comment (draft-07+; stored and round-tripped; not used for validation; reverse codegen via #[json_schema(comment = "...")]), and $schema
(stored and round-tripped; used to infer spec version when not set explicitly;
reverse codegen emits Draft 2020-12 URI by default; default is Draft 2020-12
when absent or unrecognized), and
$id (unique identifier; stored and round-tripped; we support only $id, not draft-04id; Full dedupe includes
$id in the key, Functional does not; reverse codegen via #[json_schema(id = \"...\")]; forward codegen emits the attribute when the schema has $id
so round-trip preserves it), allOf (validator: instance must validate
against every subschema; codegen: branches merged on-the-fly into one Rust
model; reverse codegen: not supported), anyOf (validator: instance must validate against at least one subschema; codegen: non-empty anyOf becomes a Rust enum with one variant per branch, including root anyOf; reverse codegen: not currently emitted), oneOf (validator: instance must validate against exactly one subschema; codegen: non-empty oneOf becomes a Rust enum with one variant per branch, including root oneOf; reverse codegen: not currently emitted), and additionalProperties (validator: false → one error per additional key; schema → validate each additional property value; codegen: false → #[serde(deny_unknown_fields)], schema → additional: BTreeMap<String, T>; reverse: closed structs emit false, BTreeMap<String, V> emits object with additionalProperties). For implementation details and design
philosophy, see design.md. Generated struct and field names are
always valid Rust identifiers (reserved words and invalid characters are
escaped; see design.md for sanitization rules).
Example
JSON Schema:
Generated Rust:
//! Generated by json-schema-rs. Do not edit manually.
use ;
Every generated struct implements ToJsonSchema (e.g. Root::json_schema()
returns a JsonSchema). Serialize to JSON with String::try_from(&schema) or
Vec::<u8>::try_from(&schema). Reverse codegen emits a flat root-level $defs
map with $ref for shared and recursive types. See design.md for
reverse codegen details. The library supports in-document $ref resolving against root
$defs / definitions containers (fragments #, #/$defs/Name,
#/definitions/Name) for both validation and Rust codegen; remote refs and
anchors are out of scope for this implementation.
Using the library
Add to your Cargo.toml:
[]
= "0.0.4"
Parse one or more schemas and generate Rust (one buffer per schema, plus an
optional shared buffer when dedupe finds identical shapes). With default
settings you can use TryFrom:
let schema = JsonSchema::try_from(schema_json)?; or schema_json.try_into()?.
For custom settings, use the constructors:
use ;
use Read;
// Default settings: use TryFrom (e.g. JsonSchema::try_from(schema_json_str)?). For custom settings:
let schema_settings = default;
// From a string:
let json_schema = new_from_str?;
// From bytes (e.g. a buffer or file contents):
let json_schema = new_from_slice?;
// From an already-parsed serde_json::Value (avoids string round-trips):
let json_schema = new_from_serde_value?;
// From a reader (e.g. std::fs::File, std::io::Stdin):
let json_schema = new_from_reader?;
// From a file path:
let json_schema = new_from_path?;
// Then generate Rust:
let code_gen_settings = builder.build;
let output = generate_rust?;
// output.per_schema.len() == 1; write output.per_schema[0] to a file or stdout
// When dedupe finds shared structs, output.shared is Some(shared_rust_code)
Structurally identical object schemas are deduplicated by default (one generated
struct per shape, emitted in a shared buffer). Use
CodeGenSettings::builder().dedupe_mode(DedupeMode::Disabled).build() to turn
this off, or DedupeMode::Functional to compare only functional fields (see
design.md). Use
JsonSchemaSettings::builder().disallow_unknown_fields(true).build() to reject
schema definitions with unknown keys. Use
CodeGenSettings::builder().model_name_source(ModelNameSource::PropertyKeyFirst).build()
to prefer property keys over title for struct names. CLI:
--jss-disallow-unknown-fields, --cgs-model-name-source title-first or
property-key, --cgs-dedupe-mode disabled|functional|full.
Using the macro (compile-time codegen)
The json_schema_to_rust! macro generates Rust types at compile time and
inlines them at the call site (no file is written). Add both crates:
[]
= "0.0.4"
= "0.0.4"
Then use json_schema_rs_macro::json_schema_to_rust and use any of these forms:
-
Single file path (relative to your crate root, i.e.
CARGO_MANIFEST_DIR):json_schema_to_rust!("path/to/schema.json") -
Multiple file paths:
json_schema_to_rust!("a.json", "b.json") -
Single inline JSON Schema string:
json_schema_to_rust!(r#"{"type":"object", "properties": {...}}"#) -
Multiple inline JSON Schema strings:
json_schema_to_rust!(r#"..."#, r#"..."#)
When you pass multiple schemas (paths or inline), each schema’s types are
emitted in a separate Rust module to avoid name collisions: one module per
JSON Schema. Module names come from the file stem for paths (e.g. simple from
simple.json) or schema_0, schema_1, … for inline strings. Use the
generated types via those modules (e.g. simple::Root, schema_0::Root).
Reverse codegen (Rust → JSON Schema). Every generated struct implements
ToJsonSchema (e.g. Root::json_schema()). Hand-written structs can use
#[derive(ToJsonSchema)] from json_schema_rs_macro with optional
#[json_schema(title = "...")] and, on fields,
#[json_schema(minimum = N, maximum = N)] to set JSON Schema bounds for
integer/number properties. Convert a schema to JSON with
String::try_from(&schema) or .try_into(). Emits $defs and $ref for shared
and recursive types. Add json-schema-rs-macro when using the derive. Details:
design.md.
Validate a JSON instance against a schema (returns all errors, no fail-fast):
use ;
use Value;
let schema: JsonSchema = from_str?;
let instance: Value = from_str?;
let result = validate;
if let Err = result
Running the binary
Build and run the CLI:
Build:
The binary is at target/release/jsonschemars.
Generate code from one or more JSON Schemas. The generate command takes
one or more INPUTs (file paths, directory paths—recursively searched for .json
files—or - for one schema from stdin) and writes generated .rs files under
the required -o output directory. Output file and directory names are
sanitized for Rust (e.g. hyphens to underscores), and each directory
includes a mod.rs so the output is a valid Rust module tree. If any schema
file fails to read, every failure is logged to stderr with its path and the
command exits without writing output. Only rust is supported today. See
design.md for details.
Validate a JSON payload against a schema (one schema, one payload; schema
via -s, payload from stdin or -p):
Both from files: jsonschemars validate -s schema.json -p payload.json. Use
-s - to read the schema from stdin (payload then from -p or stdin).
To generate into a file at build time (e.g. under OUT_DIR) instead of
using the macro, use the library API from a build.rs script:
let bytes = generate_rust(&[schema], &CodeGenSettings::builder().build())?;
then write bytes[0] to a path and include! it in your crate.
Alternative libraries
TODO
Developers
Project is under active maintenance—even if there are no recent commits. Please submit an issue or bug request if the library needs updating.
- Commands:
make lint,make test,make fix - Official JSON Schema Test Suite: To run the library against the full
JSON Schema Test Suite,
run
make vendor_test_suiteonce to clone it intoresearch/json-schema-test-suite/(gitignored), thenmake test_json_schema_suiteto run the suite. The test is ignored by default and only runs when invoked explicitly; it hard-fails if the suite directory is missing. - Implementation and philosophy: If you're curious how something was implemented or the philosophy behind our approach, see design.md.
Credits
Made by Todd Everett Griffin.