Back to Blog
Python

Python jsonschema Formats and Validation Errors

python jsonschema formats and validation errors: Learn how jsonschema format validation works, why formats are ignored by default, and how to read and handle Validatio...

jsonschemaJSON SchemaPython validationerror handlingdata validation
Illustration of a JSON document failing format validation with a ValidationError raised in Python

python jsonschema formats and validation errors requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The jsonschema library in Python does not validate format keywords unless you explicitly enable them. A schema that declares "format": "email" will happily accept "not-an-email" when you call validate() without a format checker. This surprises many developers because format validation is the first thing they expect from a schema library. The behavior comes from the JSON Schema specification, which treats format as an annotation by default rather than an assertion. This article explains how format validation works in python jsonschema, how to enable it, and how to read the validation errors it produces.

Why jsonschema Ignores Formats by Default

The JSON Schema specification defines format as an annotation, not an assertion. A validator is allowed to check it but is not required to. The jsonschema library takes the conservative path: unless you pass a format_checker, the format keyword is recorded but never evaluated. The following code raises no error even though the email address is clearly invalid:

from jsonschema import validate schema = { "type": "object", "properties": { "email": {"type": "string", "format": "email"} }, } validate({"email": "not-an-email"}, schema)

This default exists because format values are often domain-specific. A schema author may use "format": "uri" in a context where a relative reference is acceptable, or "format": "date" where only a specific calendar convention applies. Enabling format checks globally can reject data that a different consumer would accept. The library leaves that decision to the caller.

Enabling Format Validation with FormatChecker

To turn format validation on, construct a FormatChecker and pass it to validate:

from jsonschema import FormatChecker, validate schema = { "type": "object", "properties": { "email": {"type": "string", "format": "email"} }, } validate({"email": "not-an-email"}, schema, format_checker=FormatChecker())

Now the call raises ValidationError with a message that names the value and the format. The same parameter is accepted by the validator classes, so you can attach a checker once and reuse the validator:

from jsonschema import Draft202012Validator validator = Draft202012Validator(schema, format_checker=FormatChecker()) errors = validator.iter_errors({"email": "not-an-email"})

A single FormatChecker instance can be shared across validators. The checker keeps a registry of format names and their check functions, so sharing it avoids rebuilding that registry for every request.

Built-in Format Checkers and Their Behavior

The default FormatChecker() registers a set of common formats:

FormatChecks
emailBasic email structure
ipv4 / ipv6IP address syntax
uri / uri-referenceURI syntax
date-time / date / timeCalendar and clock values
hostnameHostname pattern, not DNS resolution
uuidUUID string
regexValid regular expression
json-pointerJSON Pointer syntax

Two behaviors matter in production. First, a format that has no registered checker is silently ignored. If you validate against "format": "iri" but the optional rfc3987 package is not installed, the check is skipped without warning. Second, the built-in checks are syntactic. The hostname checker verifies the pattern, not whether the host resolves, and email does not confirm that a mailbox exists.

Reading ValidationError Objects

When a format check fails, the resulting ValidationError carries structured information, not just a message:

from jsonschema import FormatChecker, ValidationError, validate schema = { "type": "object", "properties": { "email": {"type": "string", "format": "email"} }, } try: validate({"email": "not-an-email"}, schema, format_checker=FormatChecker()) except ValidationError as err: print(err.message) # message text describing the failure print(err.validator) # format print(err.validator_value) # email print(list(err.path)) # ['email'] print(err.instance) # not-an-email

path is a deque of keys or indices leading to the failing value, so list(err.path) gives the location in the document. validator and validator_value tell you which keyword failed and what its value was. This is enough to build a precise error report without parsing the message text.

Finding the Most Relevant Error with best_match

A complex schema with anyOf or oneOf can produce many errors for a single document. The first error in iteration order is not necessarily the most useful one. The best_match function selects the error whose location is deepest in the instance, which usually points at the actual problem:

from jsonschema import best_match, Draft202012Validator validator = Draft202012Validator(schema, format_checker=FormatChecker()) errors = validator.iter_errors(instance) error = best_match(errors)

best_match also inspects the context of errors raised by anyOf and oneOf, so it can surface the specific sub-error that caused the branch to fail. When you only want to show one message to a user, best_match is the right choice.

Working with Nested Errors Using ErrorTree

For reporting multiple problems at once, ErrorTree organizes errors by their location:

from jsonschema import ErrorTree errors = list(validator.iter_errors(instance)) tree = ErrorTree(errors) for path, subtree in tree.items.items(): print(path, subtree.errors)

tree.errors maps a path segment to the errors at that level, and tree.items maps a path segment to the next ErrorTree level. This lets you walk the document structure and attach error messages to the exact fields that failed, which is useful when rendering validation feedback in an API response.

Registering Custom Format Checkers

When the built-in formats do not match your domain, register a custom checker on a FormatChecker instance:

from jsonschema import FormatChecker checker = FormatChecker() @checker.checks("slug") def is_slug(value): if not isinstance(value, str): return False return all(ch.isalnum() or ch == "-" for ch in value)

The check function receives the instance value and returns True for valid, False for invalid. You can also raise an exception to produce a custom message. To make the checker available to every validator in a module without threading the instance around, use the classmethod form:

@FormatChecker.cls_checks("slug") def is_slug(value): ...

A custom checker is only invoked when the instance is a string, because format applies to strings by definition. The function should be fast and side-effect free, since it may run once per string field on every validation pass.

Performance and Production Considerations

Format checking adds measurable work to every validation. Each email or date-time check parses the string, and custom checkers run arbitrary code. For a request path that validates large payloads, the cost is proportional to the number of string fields that carry a format keyword. If throughput matters, measure the difference with and without a format_checker before enabling it globally.

The built-in checkers perform local computation only. The hostname checker does not resolve DNS, and uri does not make network requests. That keeps validation predictable, but it also means format checks are not a substitute for application-level validation such as confirming that a resource exists or that a date is within an allowed range.

The silent-skip behavior for unregistered formats is the main production risk. If a schema uses iri or idn-email and the optional dependency is missing, validation passes when it should fail. In a validation pipeline, log the set of formats the checker actually supports and compare it against the formats used by the schemas. A mismatch is a configuration error, not a data error, and it should be visible in monitoring.

python jsonschema formats and validation errors: Practical U | RYUSLOG DEV