Crate jsonschema

source
Expand description

A high-performance JSON Schema validator for Rust.

  • šŸ“š Support for popular JSON Schema drafts
  • šŸ”§ Custom keywords and format validators
  • šŸŒ Remote reference fetching (network/file)
  • šŸŽØ Basic output style as per JSON Schema spec

Ā§Supported drafts

Compliance levels vary across drafts, with newer versions having some unimplemented keywords.

  • Draft 2020-12
  • Draft 2019-09
  • Draft 7
  • Draft 6
  • Draft 4

Ā§Validation

The jsonschema crate offers two main approaches to validation: one-off validation and reusable validators.

For simple use cases where you need to validate an instance against a schema once, use the is_valid function:

use serde_json::json;

let schema = json!({"type": "string"});
let instance = json!("Hello, world!");

assert!(jsonschema::is_valid(&schema, &instance));

For better performance, especially when validating multiple instances against the same schema, build a validator once and reuse it:

use serde_json::json;

let schema = json!({"type": "string"});
let validator = jsonschema::validator_for(&schema)?;

assert!(validator.is_valid(&json!("Hello, world!")));
assert!(!validator.is_valid(&json!(42)));

// Iterate over all errors
let instance = json!(42);
let result = validator.validate(&instance);
if let Err(errors) = result {
    for error in errors {
        eprintln!("Error: {}", error);
        eprintln!("Location: {}", error.instance_path);
    }
}

Ā§Configuration

jsonschema provides several ways to configure and use JSON Schema validation.

Ā§Draft-specific Modules

The library offers modules for specific JSON Schema draft versions:

Each module provides:

  • A new function to create a validator
  • An is_valid function for quick validation
  • An options function to create a draft-specific configuration builder

Hereā€™s how you can explicitly use a specific draft version:

use serde_json::json;

let schema = json!({"type": "string"});
let validator = jsonschema::draft7::new(&schema)?;

assert!(validator.is_valid(&json!("Hello")));

You can also use the convenience is_valid function for quick validation:

use serde_json::json;

let schema = json!({"type": "number", "minimum": 0});
let instance = json!(42);

assert!(jsonschema::draft202012::is_valid(&schema, &instance));

For more advanced configuration, you can use the draft-specific options function:

use serde_json::json;

let schema = json!({"type": "string", "format": "ends-with-42"});
let validator = jsonschema::draft202012::options()
    .with_format("ends-with-42", |s| s.ends_with("42"))
    .should_validate_formats(true)
    .build(&schema)?;

assert!(validator.is_valid(&json!("Hello 42")));
assert!(!validator.is_valid(&json!("No!")));

Ā§General Configuration

For configuration options that are not draft-specific, jsonschema provides a general builder via jsonschema::options().

Hereā€™s an example of using the general options builder:

use serde_json::json;

let schema = json!({"type": "string"});
let validator = jsonschema::options()
    // Add configuration options here
    .build(&schema)?;

assert!(validator.is_valid(&json!("Hello")));

For a complete list of configuration options and their usage, please refer to the ValidationOptions struct.

Ā§Automatic Draft Detection

If you donā€™t need to specify a particular draft version, you can use jsonschema::validator_for which automatically detects the appropriate draft:

use serde_json::json;

let schema = json!({"$schema": "http://json-schema.org/draft-07/schema#", "type": "string"});
let validator = jsonschema::validator_for(&schema)?;

assert!(validator.is_valid(&json!("Hello")));

Ā§External References

By default, jsonschema resolves HTTP references using reqwest and file references from the local file system.

To enable HTTPS support, add the rustls-tls feature to reqwest in your Cargo.toml:

reqwest = { version = "*", features = ["rustls-tls"] }

You can disable the default behavior using crate features:

  • Disable HTTP resolving: default-features = false, features = ["resolve-file"]
  • Disable file resolving: default-features = false, features = ["resolve-http"]
  • Disable both: default-features = false

You can implement a custom retriever to handle external references. Hereā€™s an example that uses a static map of schemas:

use std::{collections::HashMap, sync::Arc};
use anyhow::anyhow;
use jsonschema::{Retrieve, Uri};
use serde_json::{json, Value};
use url::Url;

struct InMemoryRetriever {
    schemas: HashMap<String, Value>,
}

impl Retrieve for InMemoryRetriever {

   fn retrieve(
       &self,
       uri: &Uri<&str>,
   ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
        self.schemas
            .get(uri.as_str())
            .cloned()
            .ok_or_else(|| format!("Schema not found: {uri}").into())
    }
}

let mut schemas = HashMap::new();
schemas.insert(
    "https://example.com/person.json".to_string(),
    json!({
        "type": "object",
        "properties": {
            "name": { "type": "string" },
            "age": { "type": "integer" }
        },
        "required": ["name", "age"]
    }),
);

let retriever = InMemoryRetriever { schemas };

let schema = json!({
    "$ref": "https://example.com/person.json"
});

let validator = jsonschema::options()
    .with_retriever(retriever)
    .build(&schema)?;

assert!(validator.is_valid(&json!({
    "name": "Alice",
    "age": 30
})));

assert!(!validator.is_valid(&json!({
    "name": "Bob"
})));

Ā§Output Styles

jsonschema supports the basic output style as defined in JSON Schema Draft 2019-09. This styles allow you to serialize validation results in a standardized format using serde.

use serde_json::json;

let schema_json = json!({
    "title": "string value",
    "type": "string"
});
let instance = json!("some string");
let validator = jsonschema::validator_for(&schema_json)?;

let output = validator.apply(&instance).basic();

assert_eq!(
    serde_json::to_value(output)?,
    json!({
        "valid": true,
        "annotations": [
            {
                "keywordLocation": "",
                "instanceLocation": "",
                "annotations": {
                    "title": "string value"
                }
            }
        ]
    })
);

Ā§Custom Keywords

jsonschema allows you to extend its functionality by implementing custom validation logic through custom keywords. This feature is particularly useful when you need to validate against domain-specific rules that arenā€™t covered by the standard JSON Schema keywords.

To implement a custom keyword, you need to:

  1. Create a struct that implements the Keyword trait
  2. Create a factory function or closure that produces instances of your custom keyword
  3. Register the custom keyword with the Validator instance using the ValidationOptions::with_keyword method

Hereā€™s a complete example:

use jsonschema::{
    paths::{JsonPointer, JsonPointerNode},
    ErrorIterator, Keyword, ValidationError,
};
use serde_json::{json, Map, Value};
use std::iter::once;

// Step 1: Implement the Keyword trait
struct EvenNumberValidator;

impl Keyword for EvenNumberValidator {
    fn validate<'instance>(
        &self,
        instance: &'instance Value,
        instance_path: &JsonPointerNode,
    ) -> ErrorIterator<'instance> {
        if let Value::Number(n) = instance {
            if n.as_u64().map_or(false, |n| n % 2 == 0) {
                Box::new(None.into_iter())
            } else {
                let error = ValidationError::custom(
                    JsonPointer::default(),
                    instance_path.into(),
                    instance,
                    "Number must be even",
                );
                Box::new(once(error))
            }
        } else {
            let error = ValidationError::custom(
                JsonPointer::default(),
                instance_path.into(),
                instance,
                "Value must be a number",
            );
            Box::new(once(error))
        }
    }

    fn is_valid(&self, instance: &Value) -> bool {
        instance.as_u64().map_or(false, |n| n % 2 == 0)
    }
}

// Step 2: Create a factory function
fn even_number_validator_factory<'a>(
    _parent: &'a Map<String, Value>,
    value: &'a Value,
    _path: JsonPointer,
) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
    // You can use the `value` parameter to configure your validator if needed
    if value.as_bool() == Some(true) {
        Ok(Box::new(EvenNumberValidator))
    } else {
        Err(ValidationError::custom(
            JsonPointer::default(),
            JsonPointer::default(),
            value,
            "The 'even-number' keyword must be set to true",
        ))
    }
}

// Step 3: Use the custom keyword
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let schema = json!({"even-number": true, "type": "integer"});
    let validator = jsonschema::options()
        .with_keyword("even-number", even_number_validator_factory)
        .build(&schema)?;

    assert!(validator.is_valid(&json!(2)));
    assert!(!validator.is_valid(&json!(3)));
    assert!(!validator.is_valid(&json!("not a number")));

    Ok(())
}

In this example, weā€™ve created a custom even-number keyword that validates whether a number is even. The EvenNumberValidator implements the actual validation logic, while the even_number_validator_factory creates instances of the validator and allows for additional configuration based on the keywordā€™s value in the schema.

You can also use a closure instead of a factory function for simpler cases:

let schema = json!({"even-number": true, "type": "integer"});
let validator = jsonschema::options()
    .with_keyword("even-number", |_, _, _| {
        Ok(Box::new(EvenNumberValidator))
    })
    .build(&schema)?;

Ā§Custom Formats

JSON Schema allows for format validation through the format keyword. While jsonschema provides built-in validators for standard formats, you can also define custom format validators for domain-specific string formats.

To implement a custom format validator:

  1. Define a function or a closure that takes a &str and returns a bool.
  2. Register the function with jsonschema::options().with_format().
use serde_json::json;

// Step 1: Define the custom format validator function
fn ends_with_42(s: &str) -> bool {
    s.ends_with("42!")
}

// Step 2: Create a schema using the custom format
let schema = json!({
    "type": "string",
    "format": "ends-with-42"
});

// Step 3: Build the validator with the custom format
let validator = jsonschema::options()
    .with_format("ends-with-42", ends_with_42)
    .with_format("ends-with-43", |s| s.ends_with("43!"))
    .build(&schema)?;

// Step 4: Validate instances
assert!(validator.is_valid(&json!("Hello42!")));
assert!(!validator.is_valid(&json!("Hello43!")));
assert!(!validator.is_valid(&json!(42))); // Not a string

Ā§Notes on Custom Format Validators

  • Custom format validators are only called for string instances.
  • Format validation can be disabled globally or per-draft using ValidationOptions. Ensure format validation is enabled if youā€™re using custom formats.

Re-exportsĀ§

ModulesĀ§

StructsĀ§

  • A document with a concrete interpretation under a JSON Schema specification.
  • Configuration options for JSON Schema validation.
  • A compiled JSON Schema validator.

EnumsĀ§

  • JSON Schema specification versions.

TraitsĀ§

  • Trait that allows implementing custom validation for keywords.
  • Trait for retrieving resources from external sources.
  • SchemaResolverDeprecated
    A resolver that resolves external schema references. Internal references such as #/definitions and JSON pointers are handled internally.

FunctionsĀ§

  • compileDeprecated
    Create a validator for the input schema with automatic draft detection.
  • A shortcut for validating instance against schema. Draft is detected automatically.
  • Create a builder for configuring JSON Schema validation options.
  • Create a validator for the input schema with automatic draft detection and default options.

Type AliasesĀ§