Registry construction is now explicit: add shared schemas first, then call
prepare() to build a reusable registry. Validators no longer take ownership of
that registry; pass it by reference with with_registry(®istry).
ValidationOptions::with_resource and ValidationOptions::with_resources were
removed in favor of building a Registry first. For cases with multiple shared
schemas, extend([...]) is the batch form of add(...).
// Old (0.45.x)
use jsonschema::{Registry, Resource};
// Inline shared schema
let validator = jsonschema::options()
.with_resource(
"https://example.com/schema",
Resource::from_contents(shared_schema),
)
.build(&schema)?;
// Multiple shared schemas
let validator = jsonschema::options()
.with_resources([
(
"https://example.com/schema-1",
Resource::from_contents(schema_1),
),
(
"https://example.com/schema-2",
Resource::from_contents(schema_2),
),
].into_iter())
.build(&schema)?;
// Prebuilt registry
let registry = Registry::try_from_resources([
(
"https://example.com/schema",
Resource::from_contents(shared_schema),
),
])?;
let validator = jsonschema::options()
.with_registry(registry)
.build(&schema)?;
// New (0.46.0)
use jsonschema::Registry;
// Shared registry + borrowed validator build
let registry = Registry::new()
.add("https://example.com/schema", shared_schema)?
.prepare()?;
let validator = jsonschema::options()
.with_registry(®istry)
.build(&schema)?;
// Multiple shared schemas
let registry = Registry::new()
.extend([
("https://example.com/schema-1", schema_1),
("https://example.com/schema-2", schema_2),
])?
.prepare()?;
let validator = jsonschema::options()
.with_registry(®istry)
.build(&schema)?;The Keyword::validate signature has been simplified. Path information (instance_path and schema_path)
is now filled in automatically, so you only need to provide the error message.
Keyword::validate signature:
// Old (0.38.x)
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
) -> Result<(), ValidationError<'i>>;
// New (0.39.0)
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>>;Creating errors:
// Old (0.38.x)
ValidationError::custom(schema_path, instance_path, instance, message)
// New (0.39.0) - just the message, paths are filled in automatically
ValidationError::custom("expected a string")
// For factory errors (invalid schema values)
ValidationError::schema("expected true")Updated implementation example:
use jsonschema::{Keyword, ValidationError};
use serde_json::Value;
struct MyValidator;
impl Keyword for MyValidator {
fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
if !instance.is_string() {
return Err(ValidationError::custom("expected a string"));
}
Ok(())
}
fn is_valid(&self, instance: &Value) -> bool {
instance.is_string()
}
}The Validator type is now Send + Sync on WASM targets, restoring the expected behavior that was inadvertently broken in 0.34.0. This allows Validator to be used in static variables and shared across async contexts on WASM.
If you implemented custom Keyword or Format validators on WASM that rely on non-thread-safe types (like Rc or RefCell), you'll need to update them to use thread-safe alternatives (Arc, Mutex, etc.):
use std::sync::Arc;
// Old (0.37.x on WASM) - non-thread-safe types were allowed
use std::rc::Rc;
struct MyKeyword {
data: Rc<SomeData>,
}
// New (0.38.x) - must be Send + Sync on all platforms
struct MyKeyword {
data: Arc<SomeData>,
}Note: The Retrieve and AsyncRetrieve traits still have relaxed Send + Sync bounds on WASM, so retrievers can continue to use non-thread-safe types.
All ValidationError fields are private. Replace error.instance, error.instance_path, error.schema_path, and error.kind with the corresponding accessor calls:
let instance = error.instance();
let kind = error.kind();
let instance_path = error.instance_path();
let schema_path = error.schema_path();ErrorIterator used to be a type alias to Box<dyn ValidationErrorIterator<'_>> and is now a struct wrapping that iterator.
The legacy apply() API and its BasicOutput/OutputUnit structures have been removed in favor of
the richer Validator::evaluate
interface that exposes the JSON Schema Output v1 formats (flag/list/hierarchical) directly.
use serde_json::json;
// Old (0.35.x)
let output = validator.apply(&instance).basic();
match output {
BasicOutput::Valid(units) => println!("valid: {units:?}"),
BasicOutput::Invalid(errors) => println!("errors: {errors:?}"),
}
// New (0.36.0)
let evaluation = validator.evaluate(&instance);
if evaluation.flag().valid {
println!("valid");
}
let list = serde_json::to_value(evaluation.list())?;
let hierarchical = serde_json::to_value(evaluation.hierarchical())?;Because evaluate() materializes every evaluation step so it can provide the structured outputs, it
always walks the full schema tree. If you only need a boolean result, continue to prefer
is_valid or
validate.
The serialized JSON now matches the JSON Schema Output v1 specification
and its companion schema.
For example, evaluating an array against a schema with prefixItems and items produces list output like:
{
"valid": false,
"details": [
{"valid": false, "evaluationPath": "", "schemaLocation": "", "instanceLocation": ""},
{
"valid": false,
"evaluationPath": "/items",
"instanceLocation": "",
"schemaLocation": "/items",
"droppedAnnotations": true
},
{
"valid": false,
"evaluationPath": "/items",
"instanceLocation": "/1",
"schemaLocation": "/items"
},
{
"valid": false,
"evaluationPath": "/items/type",
"instanceLocation": "/1",
"schemaLocation": "/items/type",
"errors": {"type": "\"oops\" is not of type \"integer\""}
},
{
"valid": true,
"evaluationPath": "/prefixItems",
"instanceLocation": "",
"schemaLocation": "/prefixItems",
"annotations": 0
},
{
"valid": true,
"evaluationPath": "/prefixItems/0",
"instanceLocation": "/0",
"schemaLocation": "/prefixItems/0"
},
{
"valid": true,
"evaluationPath": "/prefixItems/0/type",
"instanceLocation": "/0",
"schemaLocation": "/prefixItems/0/type"
},
{
"valid": true,
"evaluationPath": "/type",
"instanceLocation": "",
"schemaLocation": "/type"
}
]
}If you need to inspect annotations or errors programmatically without serializing to JSON, use the
new evaluation.iter_annotations()
and evaluation.iter_errors()
helpers.
Schemas with custom/unknown $schema URIs now require their meta-schema to be registered before building validators. Custom meta-schemas automatically inherit the draft-specific behavior of their underlying draft by walking the meta-schema chain. Validators always honor an explicitly configured draft (e.g., via ValidationOptions::with_draft), so overriding the draft is still the highest priority and bypasses auto-detection and the registry check intentionally.
// Old (0.34.x) - would fail with unclear error
let schema = json!({
"$schema": "http://example.com/custom",
"type": "object"
});
let validator = jsonschema::validator_for(&schema)?;
// New (0.35.x) - explicit registration required
use jsonschema::{Registry, Resource, Draft};
let meta_schema = json!({
"$id": "http://example.com/custom",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://json-schema.org/draft/2020-12/vocab/validation": true,
}
});
let registry = Registry::try_from_resources(
[("http://example.com/custom", Resource::from_contents(meta_schema))]
)?;
let validator = jsonschema::options()
.with_registry(registry)
.build(&schema)?;Draft Resolution: Custom meta-schemas inherit draft-specific behavior from their underlying draft. For example, a custom meta-schema based on Draft 7 will preserve Draft 7 semantics (ignoring $ref siblings, validating formats by default). The validator walks the meta-schema chain to determine the appropriate draft. To override this behavior, use .with_draft() to explicitly set a draft version.
The try_* variants have been removed. The behavior has changed:
- Old (0.34.x):
try_is_validandtry_validatereturnedResult<_, ReferencingError>for unknown$schemavalues - New (0.35.x):
is_validpanics for unknown$schemavalues,validatereturnsValidationError
// Old (0.34.x)
match jsonschema::meta::try_is_valid(&schema) {
Ok(is_valid) => println!("Valid: {}", is_valid),
Err(e) => println!("Unknown schema: {}", e),
}
// New (0.35.x) - For known drafts only
let result = jsonschema::meta::is_valid(&schema); // Returns bool, panics on unknown $schema
// New (0.35.x) - For custom meta-schemas
let registry = Registry::try_from_resources([
("http://example.com/custom", Resource::from_contents(meta_schema))
])?;
let result = jsonschema::meta::options()
.with_registry(registry)
.is_valid(&schema); // Returns boolThe method now always succeeds and returns Resource directly, since draft detection no longer fails for unknown $schema values.
// Old (0.34.x)
let resource = Resource::from_contents(schema)?;
// New (0.35.x)
let resource = Resource::from_contents(schema); // No ? neededThe primitive_type module has been removed. Use jsonschema::types instead.
// Old (0.34.x)
use jsonschema::primitive_type::{PrimitiveType, PrimitiveTypesBitMap};
// New (0.35.x)
use jsonschema::types::{JsonType, JsonTypeSet};The Validator::config() method has been removed to reduce memory footprint. The validator no longer stores the configuration internally.
// Old (0.33.x)
let validator = jsonschema::validator_for(&schema)?;
let config = validator.config(); // Returns Arc<ValidationOptions>
// New (0.34.x)
// No replacement - the config is not stored after compilation
// If you need config values, keep a reference to your ValidationOptions
let options = jsonschema::options().with_draft(Draft::Draft7);
let validator = options.build(&schema)?;
// Keep `options` around if you need to access configuration laterPublic DRAFT*_META_VALIDATOR statics have been removed. Use the new draftX::meta::validator() helper functions instead. Dropping the Send + Sync bounds for retrievers means the old LazyLock statics can't store validators on wasm32 anymore, so the new helper borrows cached validators on native platforms and builds owned copies on WebAssembly.
// Old (0.33.x)
use jsonschema::DRAFT7_META_VALIDATOR;
DRAFT7_META_VALIDATOR.is_valid(&schema);
// Also removed:
use jsonschema::DRAFT4_META_VALIDATOR;
use jsonschema::DRAFT6_META_VALIDATOR;
use jsonschema::DRAFT201909_META_VALIDATOR;
use jsonschema::DRAFT202012_META_VALIDATOR;
// New (0.34.x)
let validator = jsonschema::draft7::meta::validator();
validator.is_valid(&schema);
// Or use the module-specific helper:
jsonschema::draft7::meta::is_valid(&schema);BasicOutput and Annotations no longer have lifetime parameters. This simplifies the API and uses Arc for internal ownership.
// Old (0.33.x)
fn process_output<'a>(output: BasicOutput<'a>) -> Result<(), Error> {
match output {
BasicOutput::Valid(units) => {
for unit in units {
let annotations: &Annotations<'a> = unit.annotations();
// ...
}
}
BasicOutput::Invalid(errors) => { /* ... */ }
}
Ok(())
}
// New (0.34.x)
fn process_output(output: BasicOutput) -> Result<(), Error> {
match output {
BasicOutput::Valid(units) => {
for unit in units {
let annotations: &Annotations = unit.annotations();
// ...
}
}
BasicOutput::Invalid(errors) => { /* ... */ }
}
Ok(())
}Retrieve / AsyncRetrieve on wasm32 no longer require Send + Sync.
use jsonschema::{Retrieve, Uri};
use serde_json::Value;
use std::error::Error;
// Old (0.33.x)
use std::sync::{Arc, Mutex};
struct BrowserRetriever(Arc<Mutex<JsFetcher>>);
impl Retrieve for BrowserRetriever {
fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn Error + Send + Sync>> {
self.0.lock().unwrap().fetch(uri)
}
}
// New (0.34.x)
use std::rc::Rc;
struct BrowserRetriever(Rc<JsFetcher>);
impl Retrieve for BrowserRetriever {
fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn Error + Send + Sync>> {
self.0.fetch(uri)
}
}Async retrievers follow the same pattern—switch async_trait::async_trait to async_trait::async_trait(?Send) on wasm so the implementation can hold non-thread-safe types.
// Old (0.33.x)
#[async_trait::async_trait]
impl AsyncRetrieve for BrowserRetriever {
async fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn Error + Send + Sync>> {
self.0.lock().unwrap().fetch(uri).await
}
}
// New (0.34.x, wasm32)
#[async_trait::async_trait(?Send)]
impl AsyncRetrieve for BrowserRetriever {
async fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn Error + Send + Sync>> {
self.0.fetch(uri).await
}
}In 0.33 LocationSegment::Property now holds a Cow<'_, str> and LocationSegment is no longer Copy.
If your code matches the enum and treats the property as &str, update it like this.
This change was made to support proper round-trips for JSON Pointer segments (escaped vs. unescaped forms).
// Old (0.32.x)
match segment {
LocationSegment::Property(p) => do_something(p), // p: &str
LocationSegment::Index(i) => ...
}
do_something_else(segment);
// New (0.33.0)
match segment {
LocationSegment::Property(p) => do_something(&*p), // p: Cow<'_, str>
LocationSegment::Index(i) => ...
}
// `LocationSegment` is no longer Copy, use `.clone()` if you need ownership
do_something_else(segment.clone());PrimitiveType was replaced by JsonType, and PrimitiveTypesBitMap with JsonTypeSet.
// Old (0.29.x)
use jsonschema::primitive_types::PrimitiveType;
use jsonschema::primitive_types::PrimitiveTypesBitMap;
// New (0.30.0)
use jsonschema::JsonType;
use jsonschema::JsonTypeSet;The builder methods on ValidationOptions now take ownership of self. Change your code to use method chaining instead of reusing the options instance:
// Old (0.28.x)
let mut options = jsonschema::options();
options.with_draft(Draft::Draft202012);
options.with_format("custom", |s| s.len() > 3);
let validator = options.build(&schema)?;
// New (0.29.0)
let validator = jsonschema::options()
.with_draft(Draft::Draft202012)
.with_format("custom", |s| s.len() > 3)
.build(&schema)?;If you implement the Retrieve trait, update the uri parameter type in the retrieve method:
// Old (0.28.x)
impl Retrieve for MyRetriever {
fn retrieve(&self, uri: &Uri<&str>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
// ...
}
}
// New (0.29.0)
impl Retrieve for MyRetriever {
fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
// ...
}
}This is a type-level change only; the behavior and available methods remain the same.
The Registry API has been simplified to use a consistent builder pattern. Replace direct creation methods with Registry::options():
// Before (0.28.x)
let registry = Registry::options()
.draft(Draft::Draft7)
.try_new(
"http://example.com/schema",
resource
)?;
let registry = Registry::options()
.draft(Draft::Draft7)
.try_from_resources([
("http://example.com/schema", resource)
].into_iter())?;
let registry = Registry.try_with_resource_and_retriever(
"http://example.com/schema",
resource,
retriever
)?;
// After (0.29.0)
let registry = Registry::options()
.draft(Draft::Draft7)
.build([
("http://example.com/schema", resource)
])?;
let registry = Registry::options()
.draft(Draft::Draft7)
.build([
("http://example.com/schema", resource)
])?;
let registry = Registry::options()
.retriever(retriever)
.build(resources)?;The Validator::validate method now returns Result<(), ValidationError<'i>> instead of an error iterator. If you need to iterate over all validation errors, use the new Validator::iter_errors method.
Example:
// Old (0.25.x)
let validator = jsonschema::validator_for(&schema)?;
if let Err(errors) = validator.validate(&instance) {
for error in errors {
println!("Error: {error}");
}
}
// New (0.26.0)
let validator = jsonschema::validator_for(&schema)?;
// To get the first error only
match validator.validate(&instance) {
Ok(()) => println!("Valid!"),
Err(error) => println!("Error: {error}"),
}
// To iterate over all errors
for error in validator.iter_errors(&instance) {
println!("Error: {error}");
}Replace:
JsonPointertoLocationPathChunkReftoLocationSegmentJsonPointerNodetoLazyLocation
Replace UriRef<&str> with Uri<&str> in your custom retriever implementation.
Example:
// Old (0.21.x)
use jsonschema::{UriRef, Retrieve};
struct MyCustomRetriever;
impl Retrieve for MyCustomRetriever {
fn retrieve(&self, uri: &UriRef<&str>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
// ...
}
}
// New (0.21.0)
use jsonschema::{Uri, Retrieve};
struct MyCustomRetriever;
impl Retrieve for MyCustomRetriever {
fn retrieve(&self, uri: &Uri<&str>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
// ...
}
}- Replace
SchemaResolverwithRetrieve:- Implement
Retrievetrait instead ofSchemaResolver - Use
Box<dyn std::error::Error>for error handling - Update
ValidationOptionsto usewith_retrieverinstead ofwith_resolver
- Implement
Example:
// Old (0.20.x)
struct MyCustomResolver;
impl SchemaResolver for MyCustomResolver {
fn resolve(&self, root_schema: &Value, url: &Url, _original_reference: &str) -> Result<Arc<Value>, SchemaResolverError> {
match url.scheme() {
"http" | "https" => {
Ok(Arc::new(json!({ "description": "an external schema" })))
}
_ => Err(anyhow!("scheme is not supported"))
}
}
}
let options = jsonschema::options().with_resolver(MyCustomResolver);
// New (0.21.0)
use jsonschema::{UriRef, Retrieve};
struct MyCustomRetriever;
impl Retrieve for MyCustomRetriever {
fn retrieve(&self, uri: &UriRef<&str>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
match uri.scheme().map(|scheme| scheme.as_str()) {
Some("http" | "https") => {
Ok(json!({ "description": "an external schema" }))
}
_ => Err("scheme is not supported".into())
}
}
}
let options = jsonschema::options().with_retriever(MyCustomRetriever);- Update document handling:
- Replace
with_documentwithwith_resource
- Replace
Example:
// Old (0.20.x)
let options = jsonschema::options()
.with_document("schema_id", schema_json);
// New (0.21.0)
use jsonschema::Resource;
let options = jsonschema::options()
.with_resource("urn:schema_id", Resource::from_contents(schema_json)?);Draft-specific modules are now available:
// Old (0.19.x)
let validator = jsonschema::JSONSchema::options()
.with_draft(jsonschema::Draft2012)
.compile(&schema)
.expect("Invalid schema");
// New (0.20.0)
let validator = jsonschema::draft202012::new(&schema)
.expect("Invalid schema");Available modules: draft4, draft6, draft7, draft201909, draft202012
Use the new options() function for easier customization:
// Old (0.19.x)
let options = jsonschema::JSONSchema::options();
// New (0.20.0)
let options = jsonschema::options();The following items have been renamed. While the old names are still supported in 0.20.0 for backward compatibility, it's recommended to update to the new names:
| Old Name (0.19.x) | New Name (0.20.0) |
|---|---|
CompilationOptions |
ValidationOptions |
JSONSchema |
Validator |
JSONPointer |
JsonPointer |
jsonschema::compile |
jsonschema::validator_for |
CompilationOptions::compile |
ValidationOptions::build |