Validate JSON Schema in Python
The standard library for JSON Schema validation in Python is jsonschema — install with pip install jsonschema. Its simplest API is jsonschema.validate(instance=data, schema=schema) which raises ValidationError on failure and returns None on success. For more control, Draft7Validator(schema).validate(data) gives you an explicit validator object you can reuse across many data instances. jsonschema supports JSON Schema drafts 3, 4, 6, 7, and 2019-09. This guide covers the full API: validate(), Draft7Validator, iterating over all errors, format checkers ("email", "date", "uri"), and using jsonschema in pytest tests. For a deeper look at JSON Schema syntax, see the JSON Schema tutorial and JSON Schema examples.
Test your JSON Schema against real data in the browser.
Open JSON Schema ValidatorInstall and use jsonschema.validate()
Start by installing the library, then call jsonschema.validate() with your data and schema. The function raises jsonschema.ValidationError if validation fails, or returns None silently if the data is valid.
pip install jsonschemaimport jsonschema
schema = {
"type": "object",
"required": ["name", "age"],
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
"email": {"type": "string"},
},
"additionalProperties": False,
}
# Valid data — validate() returns None (no exception)
valid_data = {"name": "Alice", "age": 30, "email": "alice@example.com"}
jsonschema.validate(instance=valid_data, schema=schema) # OK
# Invalid data — missing required field "age"
invalid_data = {"name": "Bob"}
try:
jsonschema.validate(instance=invalid_data, schema=schema)
except jsonschema.ValidationError as e:
print(e.message) # 'age' is a required property
print(list(e.path)) # [] (top-level error, no nested path)
print(list(e.schema_path)) # ['required']
# Wrong type — age is a string instead of integer
bad_type = {"name": "Carol", "age": "thirty"}
try:
jsonschema.validate(instance=bad_type, schema=schema)
except jsonschema.ValidationError as e:
print(e.message) # 'thirty' is not of type 'integer'
print(list(e.path)) # ['age']Important: validate() stops at the first error it encounters. If you need all validation errors in a single pass — for example, to show all form field errors at once — use Draft7Validator.iter_errors() instead (covered in the next section).
The three most useful attributes on a ValidationError are:
e.message— a human-readable description, e.g.'name' is a required propertye.path— adequewith the path to the failing field in your data, e.g.deque(['address', 'city']); uselist(e.absolute_path)to get a plain liste.schema_path— path in the schema that triggered the error, e.g.deque(['properties', 'age', 'type'])
Draft7Validator: reusable validator and all errors
Create a Draft7Validator once and reuse it across many objects. This compiles the schema a single time, which is significantly faster when validating thousands of records. The validator exposes three key methods:
import jsonschema
schema = {
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": {"type": "integer"},
"name": {"type": "string", "minLength": 1},
"email": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
},
}
# Create once — reuse many times (schema compiled once)
v = jsonschema.Draft7Validator(schema)
data = {"id": "not-an-int", "name": "", "age": -5}
# is_valid() — returns bool, never raises
print(v.is_valid(data)) # False
# validate() — raises ValidationError on the first error (same as jsonschema.validate())
# v.validate(data)
# iter_errors() — yields ALL ValidationError objects
errors = sorted(v.iter_errors(data), key=str)
for error in errors:
path = list(error.absolute_path)
field = path[-1] if path else "(root)"
print(f"{field}: {error.message}")
# email: 'email' is a required property
# age: -5 is less than the minimum of 0
# id: 'not-an-int' is not of type 'integer'
# name: '' is too short
# Bulk validation — validate a list of records, collect results
records = [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "", "email": "bob@example.com"}, # name too short
{"id": "x", "name": "Carol", "email": "carol@example.com"}, # id wrong type
]
for i, record in enumerate(records):
errs = list(v.iter_errors(record))
if errs:
print(f"Record {i}: {[e.message for e in errs]}")The table below shows all validator classes available in jsonschema and the JSON Schema draft each one implements:
| Class | JSON Schema Draft | Import |
|---|---|---|
Draft3Validator | Draft 3 | jsonschema |
Draft4Validator | Draft 4 | jsonschema |
Draft6Validator | Draft 6 | jsonschema |
Draft7Validator | Draft 7 (recommended) | jsonschema |
Draft202012Validator | 2020-12 | jsonschema |
jsonschema.validate() uses Draft7Validator by default. To validate against a different draft, instantiate the class directly: Draft4Validator(schema).validate(data).
Format checkers: email, date, uri, uuid
By default, jsonschema silently ignores the "format" keyword — the same behavior as Ajv without ajv-formats. To enforce format validation, pass format_checker=jsonschema.FormatChecker() to validate() or the validator constructor.
import jsonschema
schema = {
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"birth_date": {"type": "string", "format": "date"},
"website": {"type": "string", "format": "uri"},
"token": {"type": "string", "format": "uuid"},
},
}
checker = jsonschema.FormatChecker()
# Valid data
valid = {
"email": "alice@example.com",
"birth_date": "1990-06-15",
"website": "https://jsonic.io",
"token": "550e8400-e29b-41d4-a716-446655440000",
}
jsonschema.validate(instance=valid, schema=schema, format_checker=checker) # OK
# Invalid email format
bad = {"email": "not-an-email"}
try:
jsonschema.validate(instance=bad, schema=schema, format_checker=checker)
except jsonschema.ValidationError as e:
print(e.message) # 'not-an-email' is not a 'email'
# With Draft7Validator
v = jsonschema.Draft7Validator(schema, format_checker=checker)
print(v.is_valid({"email": "bad@@email"})) # FalseSome format validators require additional packages. Install them with:
# Install all optional format dependencies at once
pip install jsonschema[format]
# Or install individually:
pip install email-validator # "email" format
pip install rfc3986 # "uri" format
pip install fqdn # "hostname" formatThe most commonly used format keywords are: "email", "date" (YYYY-MM-DD), "date-time" (ISO 8601), "uri", "uuid", "ipv4", and "hostname". Without format_checker, none of these are validated — the keyword is accepted but produces no errors.
Custom error messages and raising application errors
ValidationError.message is the schema-generated message produced by jsonschema. For user-facing APIs you'll want friendlier messages. The pattern is to iterate all errors with iter_errors(), map each error's path to a field name, and build your own error structure.
import jsonschema
from collections import defaultdict
schema = {
"type": "object",
"required": ["username", "email", "age"],
"properties": {
"username": {"type": "string", "minLength": 3, "maxLength": 30},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 18},
},
}
v = jsonschema.Draft7Validator(schema, format_checker=jsonschema.FormatChecker())
def validate_user(data: dict) -> dict:
"""Returns a dict of {field: [error_messages]}. Empty dict means valid."""
field_errors = defaultdict(list)
for error in v.iter_errors(data):
# absolute_path is a deque — last element is the field name for leaf errors
path = list(error.absolute_path)
field = path[-1] if path else "__root__"
field_errors[field].append(error.message)
return dict(field_errors)
# Example: invalid submission
errors = validate_user({"username": "ab", "email": "bad", "age": 15})
print(errors)
# {
# 'username': ["'ab' is too short"],
# 'email': ["'bad' is not a 'email'"],
# 'age': ["15 is less than the minimum of 18"]
# }
# Re-raise as a different exception type for your application layer
def parse_user(data: dict) -> dict:
try:
jsonschema.validate(instance=data, schema=schema,
format_checker=jsonschema.FormatChecker())
except jsonschema.ValidationError as e:
raise ValueError(f"Invalid user data: {e.message}") from e
return data
# Flask / FastAPI pattern — return 400 with all errors
# (FastAPI example using a plain function; adapt for your framework)
def api_create_user(body: dict):
errors = validate_user(body)
if errors:
return {"status": 400, "errors": errors}
# ... proceed with valid data
return {"status": 201, "data": body}Validate nested schemas, $ref, and arrays
Real-world schemas often contain nested objects, arrays with item constraints, and reusable components referenced with $ref. All of these work out of the box with jsonschema.
Nested objects and arrays
import jsonschema
schema = {
"type": "object",
"required": ["user", "tags"],
"properties": {
"user": {
"type": "object",
"required": ["name", "address"],
"properties": {
"name": {"type": "string"},
"address": {
"type": "object",
"required": ["city"],
"properties": {
"city": {"type": "string"},
"zip": {"type": "string", "pattern": "^[0-9]{5}$"},
},
},
},
},
"tags": {
"type": "array",
"items": {"type": "string"},
"minItems": 1,
"uniqueItems": True,
},
},
}
# Valid
data = {
"user": {"name": "Alice", "address": {"city": "Paris", "zip": "75001"}},
"tags": ["python", "json"],
}
jsonschema.validate(instance=data, schema=schema) # OK
# Missing nested required field
bad = {
"user": {"name": "Bob", "address": {}}, # city is required
"tags": ["python"],
}
v = jsonschema.Draft7Validator(schema)
for error in v.iter_errors(bad):
print(list(error.absolute_path), "→", error.message)
# ['user', 'address'] → 'city' is a required propertyReusable components with $ref and definitions
import jsonschema
# Draft 7: use "definitions" + "$ref"
schema = {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"address": {
"type": "object",
"required": ["city", "country"],
"properties": {
"city": {"type": "string"},
"country": {"type": "string", "minLength": 2, "maxLength": 2},
"zip": {"type": "string"},
},
},
},
"type": "object",
"required": ["billing_address", "shipping_address"],
"properties": {
"billing_address": {"$ref": "#/definitions/address"},
"shipping_address": {"$ref": "#/definitions/address"},
},
}
data = {
"billing_address": {"city": "London", "country": "GB"},
"shipping_address": {"city": "Paris", "country": "FR", "zip": "75001"},
}
jsonschema.validate(instance=data, schema=schema) # OK
# Draft 2020-12: use "$defs" instead of "definitions"
modern_schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"address": {
"type": "object",
"required": ["city"],
"properties": {"city": {"type": "string"}},
},
},
"type": "object",
"properties": {
"address": {"$ref": "#/$defs/address"},
},
}
from jsonschema import Draft202012Validator
Draft202012Validator(modern_schema).validate({"address": {"city": "Berlin"}})Using jsonschema in pytest
# test_schema.py
import pytest
import jsonschema
USER_SCHEMA = {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": {"type": "integer"},
"name": {"type": "string", "minLength": 1},
},
}
@pytest.fixture
def validator():
return jsonschema.Draft7Validator(USER_SCHEMA)
def test_valid_user(validator):
# Should not raise
validator.validate({"id": 1, "name": "Alice"})
def test_missing_name_raises(validator):
with pytest.raises(jsonschema.ValidationError, match="'name' is a required property"):
validator.validate({"id": 2})
def test_invalid_id_type(validator):
errors = list(validator.iter_errors({"id": "abc", "name": "Bob"}))
assert len(errors) == 1
assert "is not of type" in errors[0].messageFrequently asked questions
How do I install and use jsonschema in Python?
Install with pip install jsonschema. Basic usage:
import jsonschema
jsonschema.validate(
instance={"name": "Alice", "age": 30},
schema={
"type": "object",
"required": ["name"],
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
},
},
)If the data is valid, validate() returns None. If invalid, it raises jsonschema.exceptions.ValidationError with a message attribute describing the failure. Wrap in try/except to handle invalid data gracefully.
How do I get all validation errors at once (not just the first)?
Use Draft7Validator.iter_errors():
v = jsonschema.Draft7Validator(schema)
errors = list(v.iter_errors(data)) # all errors, no exception raisedThis returns a list of all ValidationError objects without stopping at the first failure. jsonschema.validate() stops at the first error by default. Each error has message (description), path (path to the failing field in your data), and schema_path (path to the failing rule in the schema).
How do I validate email or date formats with jsonschema?
Pass format_checker=jsonschema.FormatChecker() to validate() or the validator constructor:
jsonschema.validate(data, schema, format_checker=jsonschema.FormatChecker())Then add "format": "email" (or "date", "date-time", "uri", "uuid") to your schema property. Note: some formats require extra packages — install jsonschema[format] (pip install jsonschema[format]) to get format validators for email, uri, date-time, and others. Without format_checker, the "format" keyword is silently ignored.
What JSON Schema drafts does the jsonschema Python library support?
jsonschema supports Draft 3, 4, 6, 7 (most common), and 2019-09/2020-12. Import the specific class: from jsonschema import Draft7Validator for draft 7, from jsonschema import Draft202012Validator for the latest. jsonschema.validate() uses Draft7Validator by default. To use a different draft: Draft4Validator(schema).validate(data). Most production schemas use draft 7, which is well-supported and widely compatible. To compare approaches across languages, see the guide on validating JSON Schema in JavaScript with Ajv.
How do I validate a nested object with jsonschema?
Nest properties in your schema. When validate() fails on a nested field, error.path is a deque showing the path: deque(['address', 'city']). Convert to a list: list(error.absolute_path) → ['address', 'city']. This lets you build field-level error messages for form validation. See the Python JSON parsing guide for loading JSON data before validation.
What is the difference between jsonschema and Pydantic for JSON validation in Python?
jsonschema validates a Python dict against a JSON Schema definition (a dict or JSON file) at runtime. It's ideal when the schema comes from outside your code (an API spec, a third-party schema.json, or a JSON Schema Registry). Pydantic defines models as Python classes with type annotations: class User(BaseModel): name: str; age: int. Pydantic is faster (Rust-based in v2), has better IDE support, and is the standard for FastAPI. Use jsonschema when you need JSON Schema spec compliance or schema portability; use Pydantic when building Python APIs where the schema is defined in Python code.
Validate your JSON Schema online
Paste your JSON and schema into Jsonic to catch errors before running Python.
Open JSON Schema Validator