jsonschema

repository·master·Indexed 21 days ago

https://github.com/stranger6667/jsonschema

A high-performance JSON Schema validator for Rust supporting multiple drafts with runtime and compile-time validation options. The project includes jsonschema-cli for validating JSON instances and bundling external $ref targets, as well as jsonschema-rs, a Python binding that provides automatic draft detection, custom format/keyword implementation, and structured error reporting via the evaluate API.

Tokens
63.8K
Snippets
207
Records
250
Agent score
73%

What's inside jsonschema

  1. Understand the Benchmark Scenarios

    master

    The benchmark suite uses several distinct scenarios to test performance across different schema and instance sizes:

    BenchmarkDescriptionSchema SizeInstance Size
    OpenAPIZuora API validated against OpenAPI 3.0 schema18 KB4.5 MB
    SwaggerKubernetes API (v1.10.0) with Swagger schema25 KB3.0 MB
    GeoJSONCanadian border in GeoJSON format4.8 KB2.1 MB
    CITMConcert data catalog with inferred schema2.3 KB501 KB
    FastFrom fastjsonschema benchmarks (valid/invalid)595 B55 B / 60 B
    FHIRPatient example validated against FHIR schema3.3 MB2.1 KB
    RecursiveNested data with $dynamicRef1.4 KB449 B
  2. Use a Schema Registry for frequent schema reuse

    master

    For applications that frequently use the same schemas, use JSONSchema::Registry. This allows you to pre-register schemas and reference them by URI, which is more efficient than re-loading them. A registry can also be configured with a specific draft: version and a custom retriever:.

    registry = JSONSchema::Registry.new([
      ["https://example.com/address.json", {
        "type" => "object",
        "properties" => {
          "street" => { "type" => "string" },
          "city" => { "type" => "string" }
        }
      }],
      ["https://example.com/person.json", {
        "type" => "object",
        "properties" => {
          "name" => { "type" => "string" },
          "address" => { "$ref" => "https://example.com/address.json" }
        }
      }]
    ])
    
    validator = JSONSchema.validator_for(
      { "$ref" => "https://example.com/person.json" },
      registry: registry
    )
  3. Compare `jsonschema` Dynamic vs Codegen `is_valid`

    master

    The jsonschema crate provides two ways to validate data. The benchmark suite compares the performance of the dynamic is_valid method against the code-generated (codegen) is_valid method. In most scenarios, the Codegen approach provides significant speedups (e.g., ~12x for FHIR and ~10x for Recursive schemas).

    | Benchmark | Dynamic `is_valid` | Codegen `is_valid` | Speedup |
    |-----------|--------------------|--------------------|---------|
    | OpenAPI   | 1.16 ms             | 459.35 µs          | **2.53x** |
    | Swagger   | 1.38 ms             | 513.96 µs          | **2.69x** |
    | GeoJSON   | 370.51 µs           | 61.784 µs          | **6.00x** |
    | CITM      | 346.39 µs           | 131.13 µs          | **2.64x** |
    | Fast (Valid) | 64.854 ns        | 10.815 ns          | **6.00x** |
    | Fast (Invalid) | 6.0212 ns        | 2.5233 ns          | **2.39x** |
    | FHIR      | 3.82 µs             | 315.64 ns          | **12.10x** |
    | Recursive | 6.47 µs             | 638.05 ns          | **10.14x** |
  4. Handle arbitrary-precision numbers

    master

    The Python bindings support arbitrary-precision numbers from the Rust core. Numeric values are mapped to Python types as follows:

    • Integers: Always returned as standard Python int objects.
    • Standard Floats: Values fitting IEEE-754 become Python floats.
    • High-precision/Large Floats: Values that exceed float capacity (e.g., 1e10000) are returned as decimal.Decimal objects.

    When a validation error occurs involving these numbers, the ValidationError.kind attribute may contain a Decimal instance. You should import Decimal from the decimal module to perform exact comparisons.

    from decimal import Decimal
    from jsonschema_rs import ValidationError, validator_for
    
    validator = validator_for('{"const": 1e10000}')
    try:
        validator.validate(0)
    except ValidationError as exc:
        # exc.kind.expected_value will be a Decimal
        assert exc.kind.expected_value == Decimal("1e10000")
  5. Understand benchmark methodology and library patterns

    master

    The benchmark suite measures performance across different validation patterns. It is important to note that not all libraries support the same execution model, which affects how timing is measured:

    • Pre-compilation pattern: jsonschema_rs and json_schemer support pre-compiling a schema into a reusable validator object. The benchmark measures only the validation time for these libraries.
    • Class method pattern: json-schema only provides class methods (e.g., JSON::Validator.validate). Because it lacks a pre-compilation mechanism, each iteration includes the overhead of schema processing.
    • String-based/Parsing pattern: rj_schema accepts the schema as a string argument to validate(). This means each iteration includes schema re-parsing. Additionally, because rj_schema operates on JSON strings rather than parsed Ruby objects, its timings include JSON parsing overhead.
  6. Implement custom keywords

    master

    Extend JSON Schema by creating classes for custom keywords. A custom keyword class must implement:

    1. __init__(self, parent_schema, value, schema_path): To receive the keyword value during compilation.
    2. validate(self, instance): To perform runtime validation. If validation fails, raise an exception. The original exception is preserved as the __cause__ of the resulting ValidationError.
    import jsonschema_rs
    
    class DivisibleBy:
        def __init__(self, parent_schema, value, schema_path):
            self.divisor = value
    
        def validate(self, instance):
            if isinstance(instance, int) and instance % self.divisor != 0:
                raise ValueError(f"{instance} is not divisible by {self.divisor}")
    
    validator = jsonschema_rs.validator_for(
        {"type": "integer", "divisibleBy": 3},
        keywords={"divisibleBy": DivisibleBy},
    )
    validator.is_valid(9)   # True
    validator.is_valid(10)  # False
  7. Access ValidationError fields via methods (0.36.x to 0.37.0)

    master

    In version 0.37.0, ValidationError fields became private (opaque). You must use accessor methods to retrieve error information.

    // Replace direct field access with method calls:
    let instance = error.instance();
    let kind = error.kind();
    let instance_path = error.instance_path();
    let schema_path = error.schema_path();
  8. Register custom meta-schemas (v0.35.0)

    master

    In version 0.35.0, schemas with custom or unknown $schema URIs require their meta-schema to be explicitly registered using a Registry before you can build a validator.

    Custom meta-schemas automatically inherit the draft-specific behavior of their underlying draft by walking the meta-schema chain. To override this behavior and explicitly set a draft version, use .with_draft() in your ValidationOptions.

    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)?;
  9. Install jsonschema_rs via Gemfile

    master

    To use the high-performance JSON Schema validator in your Ruby project, add the following to your Gemfile:

    gem 'jsonschema_rs'

    Pre-built native gems are available for Linux (x86_64, aarch64 glibc/musl), macOS (x86_64, arm64), and Windows (x64 mingw-ucrt). If no pre-built gem is available, it will compile from source using Ruby 3.2+ and the Rust toolchain.

  10. Run JSON Schema validation benchmarks with the benchmark crate

    master

    The benchmark crate provides a helper for running JSON Schema validation benchmarks uniformly across different libraries. It includes a predefined set of JSON schemas and instances. To use it, iterate over Benchmark::iter() and provide a closure to benchmark.run(). The closure receives the schema_name, instance_name, the schema itself, and the instance data.

    use benchmark::Benchmark;
    
    for benchmark in Benchmark::iter() {
        benchmark.run(&mut |schema_name, instance_name, schema, instance| {
            // Your benchmarking code here
        });
    }