mrkeyoor.com_
Sun 20 Sept 07:01 UTC
PyPIUtilsupdated 20 Sept 2026

jsonschema review

jsonschema 4.26.0 checks Python values against JSON Schema drafts 3, 4, 6, 7, 2019-09, and 2020-12. The one-shot `validate()` function raises on one failure; draft-specific validator objects can be reused, yield all failures, report paths into the instance and schema, and resolve `$ref` through a `referencing.Registry`. Validation does not turn numeric strings into numbers or insert defaults. Format keywords such as `email` remain annotations until a `FormatChecker` is supplied. Our Python 3.12 import finished in 0.34 seconds. Version 4.26.0's runtime change avoids importing `urllib.request` during validator import, while its documentation now covers the `uuid` format.

Verdict

jsonschema 4.26.0 installed in 0.3 seconds, used 3 MB across 6 packages, imported in 0.34 seconds, and had 0 audit findings in our sandbox. Choose it when JSON Schema itself is the shared contract; choose a model library for coercive Python boundaries, and do not assume `format`, `default`, or remote references are automatic.

We installed it

Lab card: what happened when we installed jsonschemaScreenshot of jsonschema documentation
Install✓ · 0.3s6 packages on disk · 3 MB
Importimport jsonschema in 0.34s · pure Python · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does jsonschema install cleanly?

Yes. In a fresh container with an empty cache, pip install jsonschema finished in 0.3s, leaving 6 packages and 3 MB on disk. pip-audit reported no known vulnerabilities.

What does jsonschema need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import jsonschema succeeded in 0.34s.

jsonschema or fastjsonschema: which should you use?

fastjsonschema: Use it when one fixed schema dominates a measured hot path and generated validation code is acceptable. jsonschema 4.26.0 installed in 0.3 seconds, used 3 MB across 6 packages, imported in 0.34 seconds, and had 0 audit findings in our sandbox.

When should you not use jsonschema?

The boundary is owned entirely by typed Python models that should parse, coerce, and serialize values. Pydantic or msgspec combines those tasks with validation.

API stability4/5The package continues to expose `validate`, `ValidationError`, and named validator classes for drafts from 3 through 2020-12, so old schemas do not have to move to the newest dialect. The substantial compatibility boundary is reference handling: `RefResolver` is deprecated and current applications pass a `referencing.Registry`. Version 4.26.0 makes a narrow import-time change rather than altering validation results, but custom resolvers still face real migration work.
Docs5/5Read the Docs separates schema validation, format activation, error objects, relevance heuristics, validator extension, type checking, and reference registries. The README explicitly says that format extras and a `format` keyword do not activate checking by themselves. Version 4.26.0 also documents UUID handling. Reference setup spans jsonschema and referencing documentation, so that advanced path takes more reading than basic validation.
Maintenance4/5Version 4.26.0 was published on 2026-01-07, the repository was pushed on 2026-08-24, and GitHub reports 64 open issues and pull requests in an unarchived project. The release removes an unnecessary import-time `urllib.request` dependency and updates format documentation. Supporting six published JSON Schema draft families plus companion specification and reference packages creates a broad maintenance surface even with active work.
Ecosystem5/5PyPI Stats counted 139,735,948 downloads in the latest week, and GitHub shows 4,976 stars. JSON Schema contracts move between Python, JavaScript, API descriptions, editors, and configuration tools, which is a broader interoperability story than a Python-only model. Companion projects such as referencing and jsonschema-specifications supply dialect resources, while check-jsonschema covers command-line and pre-commit use.

Discussed on

  1. hnPg_jsonschema – JSON Schema Support for Postgres219 points
  2. hnShow HN: Pg_jsonschema – A Postgres extension for JSON validation203 points
  3. hnShow HN: JSONSchema to TypeScript compiler72 points

Use it if

  • The schema is a language-neutral contract shared with JavaScript, APIs, editors, or configuration files.
  • An application must select a JSON Schema draft explicitly and return precise paths for several validation failures.
  • References should resolve from a controlled in-memory catalogue or retrieval function rather than ad hoc URL handling.
  • Users or external systems supply the schema, so Python class definitions cannot be the only source of truth.
Skip it if

Setup reality

We installed jsonschema 4.26.0 in a fresh Python 3.12 Bookworm sandbox in 0.3 seconds. The environment ended with 6 packages and 3 MB on disk. pip-audit found 0 known vulnerabilities. Distribution metadata lists 21 direct dependencies, Python 3.10 or newer, pure Python code, no py.typed marker, and an unknown package license value. import jsonschema succeeded in 0.34 seconds.

Choose the draft deliberately and call check_schema() during startup. The convenience validate() function first selects a validator from $schema, or uses the latest draft when that keyword is missing, and checks the schema before validating the instance. That behavior is useful for occasional input and repeated overhead in a record loop. Keep one Draft202012Validator or other draft object for a batch.

Version 4.26.0 has 2 named format extras: jsonschema[format] and jsonschema[format-nongpl]. They supply optional checker libraries, yet validation still needs format_checker=FormatChecker(). Availability varies by installed extras. Schema defaults also do nothing to the input. Code that extends a validator to mutate instances should isolate that behavior because validation order can make default insertion surprising inside nested or failing branches.

References now belong to referencing.Registry. Preload trusted Resource objects when possible. A retrieval callback may read files or make requests, so validate URI schemes, confine filesystem paths, set network timeouts, and avoid resolving untrusted $ref values with ambient access. iter_errors() is lazy, but anyOf, oneOf, and nested arrays can produce a large error tree. Sort by absolute_path and cap client responses while retaining full errors in internal diagnostics.

Patterns

Raise on an invalid value validate-one-instance

from jsonschema import validate, ValidationError

schema = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {"price": {"type": "number"}},
    "required": ["price"],
}

try:
    validate({"price": "9.99"}, schema)
except ValidationError as error:
    print(error.message)

`validate()` raises one `ValidationError` and checks the schema first. It does not convert the string `"9.99"` to a number.

Check a schema once for a batch reuse-validator

from jsonschema import Draft202012Validator

Draft202012Validator.check_schema(schema)
validator = Draft202012Validator(schema)

valid_documents = [
    document for document in documents
    if validator.is_valid(document)
]

The explicit class locks behavior to Draft 2020-12. Reusing the validator avoids selecting a draft and checking the same schema for every document.

Return stable paths for every failure collect-validation-errors

from jsonschema import Draft202012Validator

validator = Draft202012Validator(schema)
errors = sorted(
    validator.iter_errors(instance),
    key=lambda error: list(error.absolute_path),
)
problems = [
    {
        "path": list(error.absolute_path),
        "message": error.message,
        "validator": error.validator,
    }
    for error in errors[:50]
]

`iter_errors()` is lazy and can produce many nested failures. Sorting makes output repeatable, and this example limits the client payload to 50 problems.

Choose one relevant nested failure select-best-error

from jsonschema import Draft202012Validator
from jsonschema.exceptions import best_match

error = best_match(
    Draft202012Validator(schema).iter_errors(instance)
)
if error is not None:
    raise ValueError(f"invalid payload at {list(error.absolute_path)}: {error.message}")

`best_match()` applies a relevance heuristic. Keep the original error collection when a UI needs every field-level problem or branch context.

Reject an invalid formatted string activate-format-checks

# install with: pip install 'jsonschema[format]'
from jsonschema import Draft202012Validator, FormatChecker

validator = Draft202012Validator(
    {"type": "string", "format": "email"},
    format_checker=FormatChecker(),
)
validator.validate("person@example.com")

The extra installs available format dependencies, and `FormatChecker()` activates them. Omitting either step can leave `format` as annotation only.

Resolve `$ref` from memory register-local-reference

from jsonschema import Draft202012Validator
from referencing import Registry, Resource

address = Resource.from_contents({
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "required": ["city"],
    "properties": {"city": {"type": "string"}},
})
registry = Registry().with_resource("urn:example:address", address)
root = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "properties": {"home": {"$ref": "urn:example:address"}},
}
validator = Draft202012Validator(root, registry=registry)

A registered `Resource` resolves the URN without network or filesystem access. This is the current replacement for common `RefResolver` store patterns.

Map approved HTTPS references to local files restrict-reference-retrieval

import json
from pathlib import Path
from referencing import Registry, Resource
from referencing.exceptions import NoSuchResource

SCHEMA_ROOT = Path('/srv/app/schemas').resolve()

def retrieve(uri: str):
    prefix = 'https://schemas.example.com/'
    if not uri.startswith(prefix):
        raise NoSuchResource(ref=uri)
    path = (SCHEMA_ROOT / uri.removeprefix(prefix)).resolve()
    if SCHEMA_ROOT not in path.parents:
        raise NoSuchResource(ref=uri)
    return Resource.from_contents(json.loads(path.read_text()))

registry = Registry(retrieve=retrieve)

The callback rejects other origins and path escapes before reading. An unrestricted retriever can turn an untrusted `$ref` into local-file or network access.

Reuse one shape with `$defs` define-reusable-schema

schema = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$defs": {
        "money": {
            "type": "number",
            "minimum": 0,
        }
    },
    "type": "object",
    "properties": {
        "subtotal": {"$ref": "#/$defs/money"},
        "tax": {"$ref": "#/$defs/money"},
    },
}

Draft 2020-12 uses `$defs` for local reusable schemas. Older drafts may use `definitions`, so match the keyword to the declared `$schema`.

Reject unknown object properties validate-exact-object-shape

schema = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer", "minimum": 0},
    },
    "required": ["name"],
    "additionalProperties": False,
}

Object schemas allow undeclared keys unless `additionalProperties` is false. `required` controls presence separately from allowed property names.

Require exactly one payload shape choose-one-schema-branch

schema = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "oneOf": [
        {
            "type": "object",
            "required": ["email"],
            "properties": {"email": {"type": "string"}},
        },
        {
            "type": "object",
            "required": ["phone"],
            "properties": {"phone": {"type": "string"}},
        },
    ]
}

`oneOf` requires exactly one branch to validate. Overlapping branches can make an apparently valid instance fail because two branches matched.

Teach a validator about a domain number customize-number-type

from decimal import Decimal
from jsonschema import Draft202012Validator, validators

def is_number(checker, instance):
    return Draft202012Validator.TYPE_CHECKER.is_type(instance, 'number') or isinstance(instance, Decimal)

type_checker = Draft202012Validator.TYPE_CHECKER.redefine('number', is_number)
DecimalValidator = validators.extend(
    Draft202012Validator,
    type_checker=type_checker,
)
DecimalValidator({"type": "number"}).validate(Decimal('1.25'))

A redefined type changes validation semantics for this validator class only. Keep such extensions named because they no longer match every implementation's default behavior.

Fail during startup on a broken schema inspect-schema-error

from jsonschema import Draft202012Validator, SchemaError

try:
    Draft202012Validator.check_schema({
        "type": 42,
    })
except SchemaError as error:
    raise RuntimeError(
        f"invalid application schema at {list(error.absolute_path)}: {error.message}"
    ) from error

`check_schema()` raises `SchemaError`, which is distinct from an instance `ValidationError`. Run it when loading application-owned schemas rather than on each request.

Alternatives

PackageRegistryPick it when
fastjsonschemaPyPIUse it when one fixed schema dominates a measured hot path and generated validation code is acceptable.
pydanticPyPIUse it for Python-owned models that need parsing, coercion, serialization, and type-hint integration.
msgspecPyPIUse it when typed Python structures and fast JSON or MessagePack decoding matter more than arbitrary JSON Schema input.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.