What Is JSON Schema?
JSON Schema is a specification (currently at draft 2020-12) for describing the structure of JSON documents. A schema is itself a JSON document that declares what fields a valid document must have, what types their values must be, what patterns strings must match, what ranges numbers must fall in, and what shape nested arrays and objects must take. Given a schema and a data document, a validator answers a single question: does this data satisfy the schema?
JSON Schema is the backbone of API contracts (OpenAPI 3.0 uses draft-07), config file validation (VS Code's settings, GitHub Actions, Ansible), form generation (Uniforms, JSON Forms, React JSONSchema Form), and data pipeline verification (Airbyte, dbt, Great Expectations).
A Minimal JSON Schema Example
Schema for a user record:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 130 },
"role": { "type": "string", "enum": ["admin", "user", "guest"] }
},
"required": ["id", "email", "role"],
"additionalProperties": false
}Valid data:
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "[email protected]",
"age": 30,
"role": "admin"
}Notice "additionalProperties": false. Without it, adding "evil_field": "bypass" would silently pass. For anything security-sensitive (API endpoints, auth requests, config uploads) always set additionalProperties: false.
Validate JSON Schema in Node.js with Ajv
Ajv ("Another JSON Validator") is the fastest and most popular JS validator. It compiles schemas to native JavaScript at load time, so validation calls are essentially a single function invocation — no schema traversal at runtime.
// npm install ajv ajv-formats
const Ajv = require('ajv');
const addFormats = require('ajv-formats');
const schema = {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0 },
},
required: ['id', 'email'],
additionalProperties: false,
};
const ajv = new Ajv({ allErrors: true, strict: true });
addFormats(ajv);
const validate = ajv.compile(schema);
const data = { id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', email: '[email protected]', age: 30 };
const valid = validate(data);
if (!valid) {
console.log('Validation errors:');
console.log(validate.errors);
} else {
console.log('Valid');
}Draft 2020-12: import from ajv/dist/2020 instead of ajv. Draft-07 (still the OpenAPI 3.0 default) is the standard import.
Validate JSON Schema in Python
# pip install jsonschema
import json
import jsonschema
from jsonschema import Draft202012Validator
schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {"type": "string", "format": "uuid"},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0},
},
"required": ["id", "email"],
"additionalProperties": False,
}
data = {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "[email protected]",
"age": 30,
}
# Simplest — raises ValidationError on first failure
try:
jsonschema.validate(instance=data, schema=schema)
print("Valid")
except jsonschema.exceptions.ValidationError as e:
print(f"Invalid: {e.message} at {list(e.path)}")
# Collect all errors instead of stopping at first
validator = Draft202012Validator(schema)
errors = sorted(validator.iter_errors(data), key=lambda e: list(e.path))
for err in errors:
print(f"{'/'.join(str(p) for p in err.path)}: {err.message}")Fast Python Validation with fastjsonschema
For hot paths (request validation, streaming pipelines), fastjsonschemais 20-30x faster than the reference library. It compiles the schema to Python bytecode at compile time.
# pip install fastjsonschema
import fastjsonschema
validate = fastjsonschema.compile(schema)
try:
validate(data)
print("Valid")
except fastjsonschema.JsonSchemaException as e:
print(f"Invalid: {e.message}")Common JSON Schema Keywords
type— one ofstring,number,integer,boolean,array,object,null. Can be an array for union types.required— array of property names that must be present.properties— object mapping property names to sub-schemas.additionalProperties—falseto reject unknown keys, or a schema that all unknown keys must match.pattern— a regex string values must match (ECMAScript regex flavour).format— semantic hint:email,uri,uuid,date,date-time,ipv4,ipv6,hostname. Requires ajv-formats or python-jsonschema's format checker.minLength/maxLength— string length bounds.minimum/maximum— numeric bounds.exclusiveMinimum/exclusiveMaximumfor strict inequalities.enum— array of allowed literal values.const— single allowed value (equivalent to a 1-element enum).items— schema every array element must match.minItems/maxItems— array length bounds.uniqueItems—trueto reject duplicate array elements.anyOf/oneOf/allOf— combinatorial rules for polymorphic types.
Validating an OpenAPI Request Body
OpenAPI 3.0 uses JSON Schema draft-07 in its components.schemassection. To validate a request body against an OpenAPI schema:
# pip install openapi-spec-validator openapi-core
from openapi_core import Spec, unmarshal_request
from openapi_core.contrib.requests import RequestsOpenAPIRequest
spec = Spec.from_file_path('openapi.yaml')
# Now every incoming request can be validated:
result = unmarshal_request(RequestsOpenAPIRequest(incoming_request), spec=spec)
if result.errors:
print('Bad request:', result.errors)Common Validation Gotchas
- additionalProperties: true is the default. Extra fields pass silently. Always set to
falsefor API bodies, or you leak untrusted data into your system. - type: "integer" matches 1.0. JSON has no separate integer type; the value
1.0is considered an integer if it has no fractional part. Use"type": "number", "multipleOf": 1if you specifically want to reject1.0. - format is a hint, not a check. By default, validators do not enforce
format: "email". You must enable format checking (Ajv: add ajv-formats; Python: passformat_checker=Draft202012Validator.FORMAT_CHECKER). - Regex flavour matters. JSON Schema
patternuses ECMAScript regex. Python'sremodule differs on lookbehind and named groups. jsonschema usesreby default which can lead to subtle mismatches with Node validators. - $ref must be resolvable. If your schema references
#/definitions/Foo(draft-07) or#/$defs/Foo(2019-09+), the validator loads only what it can reach. Circular refs usually work; broken refs fail loudly.
Key Facts
- Current draft:
- 2020-12 (json-schema.org/draft/2020-12)
- Most deployed:
- draft-07 (OpenAPI 3.0, VS Code settings, GitHub Actions)
- Node library:
- Ajv (fastest, most popular) — npm install ajv
- Python library:
- jsonschema (reference), fastjsonschema (faster)
- CLI:
- ajv-cli, check-jsonschema (pre-commit friendly)
- Format enforcement:
- Opt-in — enable ajv-formats or Python format_checker
Related JSON Tools
- Format JSON Online — pretty print before validation
- Validate JSON Online — syntax validation (not schema)
- JSON Formatter Python — Python JSON handling
- JSON Formatter JavaScript — Node.js JSON handling
- JSON Lint Online — strict syntax checking
- Fix Invalid JSON — repair before validation