jsonschema

repository·main·Indexed 26 days ago

https://github.com/python-jsonschema/jsonschema

An implementation of JSON Schema validation for Python. The repository includes the JSON Schema Test Suite, a language-agnostic collection of test cases used to verify validator implementations against various specification versions, including specialized suites for annotations and output validation.

Tokens
14.6K
Snippets
23
Records
95
Agent score
89%

What's inside jsonschema

  1. Overview of JSON Schema Test Suite

    main

    The JSON Schema Test Suite is a collection of JSON objects designed for implementers of JSON Schema validation libraries to verify their validators against the official specification. It is language-agnostic and requires only a JSON parser. The suite is intended to exercise prescribed specification behavior and should not be used as a style guide for writing schemas.

    Recommended Workflow: Clone the main branch of the repository as a git submodule or git subtree to ensure you are using a stable version.

  2. Resolve references from YAML files

    main

    The retrieve callable in a referencing.Registry can be used to load schemas from any format, including YAML. Ensure that the deserialized YAML content is compatible with the JSON data model (e.g., mapping keys must be strings) to avoid issues with JSON Schema validation.

    from pathlib import Path
    import yaml
    from referencing import Registry, Resource
    from referencing.exceptions import NoSuchResource
    
    SCHEMAS = Path("/tmp/yaml-schemas")
    
    def retrieve_yaml(uri: str):
        if not uri.startswith("http://localhost/"):
            raise NoSuchResource(ref=uri)
        path = SCHEMAS / Path(uri.removeprefix("http://localhost/"))
        contents = yaml.safe_load(path.read_text())
        return Resource.from_contents(contents)
    
    registry = Registry(retrieve=retrieve_yaml)
  3. Configure custom referencing behavior in jsonschema

    main

    To customize how $ref and $dynamicRef keywords are resolved in your schemas, you must configure the referencing library. This is a two-step process:

    1. Create a referencing.Registry object that defines your set of schemas and how they are retrieved.
    2. Pass this referencing.Registry instance to your Validator when you instantiate it.

    This allows you to control the behavior of schema references across different JSON Schema versions and URI mappings.

  4. Enable format validation in jsonschema

    main
    By default, jsonschema does not validate the format keyword. This behavior is consistent with the JSON Schema specification (as of draft2019-09), which treats format as informational. To perform actual validation of formats (e.g., checking if a string is a valid date), you must explicitly provide a format_checker using the jsonschema.FormatChecker object.
  5. Run the jsonschema test suite

    main
    To run the full test suite across all supported Python versions, use nox in the source directory. If you do not have all supported Python interpreters installed, use the --no-error-on-missing-interpreters flag. Alternatively, you can run tests for a single version using your preferred test runner; tests are located in the jsonschema.tests package.
  6. Implement custom handlers using the Registry retrieve argument

    main

    To replace the handlers functionality from _RefResolver (used for supporting custom HTTP schemes), pass a custom retrieve function to the referencing.Registry constructor. This function should take a uri string and return the appropriate resource.

    from urllib.parse import urlsplit
    from referencing import Registry
    
    def retrieve(uri: str):
        parsed = urlsplit(uri)
        if parsed.scheme == "file":
            ...
        elif parsed.scheme == "custom":
            ...
    
    registry = Registry(retrieve=retrieve)
  7. Implement default value setting in schemas

    main

    The JSON Schema specification does not require the default keyword to modify the instance. To automatically populate default values into your Python objects during validation, you must extend a validator class to include a custom implementation for the properties keyword.

    When implementing this, ensure that the default values themselves are valid under the schema, as they are applied before the properties are validated. Additionally, for nested objects to receive defaults, the parent object must also have a default value defined in the schema.

    from jsonschema import Draft202012Validator, validators
    
    
    def extend_with_default(validator_class):
        validate_properties = validator_class.VALIDATORS["properties"]
    
        def set_defaults(validator, properties, instance, schema):
            for property, subschema in properties.items():
                if "default" in subschema:
                    instance.setdefault(property, subschema["default"])
    
            for error in validate_properties(
                validator, properties, instance, schema,
            ):
                yield error
    
        return validators.extend(
            validator_class, {"properties" : set_defaults},
        )
    
    
    DefaultValidatingValidator = extend_with_default(Draft202012Validator)
    
    # Example usage:
    obj = {}
    schema = {'properties': {'foo': {'default': 'bar'}}}
    # Note: jsonschema.validate(obj, schema, cls=DefaultValidatingValidator)
    # will not work because the metaschema contains `default` keywords.
    DefaultValidatingValidator(schema).validate(obj)
    assert obj == {'foo': 'bar'}
  8. Report validation errors during recursion

    main

    When implementing a validating function that checks against a subschema, you must use descend instead of iter_errors to report errors.

    To ensure the error location is correctly tracked within the instance or the schema, you must pass the path and/or schema_path arguments to the descend call when recursing into the instance or schema.

  9. Customize type checking with TypeChecker

    main

    A Validator uses an associated TypeChecker to handle the type keyword. By default, jsonschema uses optimized checks (e.g., checking for int instead of numbers.Integral for performance).

    To add custom types or change how existing types are validated, you can use TypeChecker.redefine() to create a new checker and then use jsonschema.validators.extend() to create a new Validator class.

    from jsonschema import validators, Draft202012Validator
    
    class MyInteger:
        pass
    
    def is_my_int(checker, instance):
        return (
            Draft202012Validator.TYPE_CHECKER.is_type(instance, "number") or
            isinstance(instance, MyInteger)
        )
    
    type_checker = Draft202012Validator.TYPE_CHECKER.redefine(
        "number", is_my_int,
    )
    
    CustomValidator = validators.extend(
        Draft202012Validator,
        type_checker=type_checker,
    )
    validator = CustomValidator(schema={"type" : "number"})
  10. Configure Remote References for the Test Suite

    main

    The test suite uses remote references (defined in refRemote.json within each version directory) to test $ref keyword behavior. You can register these in two ways:

    1. Filesystem Retrieval: Load schemas directly from the remotes/ directory. Map the retrieval URI to http://localhost:1234/ followed by the relative path from the remotes/ directory. For example, a $ref to http://localhost:1234/foo/bar/baz.json should resolve to remotes/foo/bar/baz.json.
    2. JSON Object Retrieval: Execute the suite's utility to get a combined JSON object of all remotes.

    Note: When running tests in the optional/format subdirectory, ensure your validator is configured to enable format validation if supported.

    $ bin/jsonschema_suite remotes