JSON Schema lets you define the exact structure and types of a JSON document and validate data against that definition. It's essential for API contracts, configuration validation, and any system that processes external JSON data.
What JSON Schema does
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["name", "email"],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"email": {
"type": "string",
"format": "email"
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"role": {
"type": "string",
"enum": ["admin", "user", "guest"]
}
},
"additionalProperties": false
}
This schema says:
- The document must be an object
-
nameandemailare required -
namemust be a string between 1–100 characters -
emailmust match the email format -
age, if present, must be an integer 0–150 -
role, if present, must be one of three values - No other properties are allowed
A JSON Schema validator lets you test JSON against a schema and see exactly which validation rules fail.
Core keywords
Type constraints:
{"type": "string"}
{"type": "number"}
{"type": "integer"}
{"type": "boolean"}
{"type": "null"}
{"type": "array"}
{"type": "object"}
{"type": ["string", "null"]} // Multiple allowed types
String constraints:
{
"type": "string",
"minLength": 5,
"maxLength": 100,
"pattern": "^[a-zA-Z0-9]+$" // Regex pattern
}
Number constraints:
{
"type": "number",
"minimum": 0,
"maximum": 100,
"exclusiveMinimum": 0, // > 0 (exclusive)
"exclusiveMaximum": 100, // < 100 (exclusive)
"multipleOf": 5 // Must be divisible by 5
}
Array constraints:
{
"type": "array",
"items": {"type": "string"}, // Each item must be a string
"minItems": 1,
"maxItems": 10,
"uniqueItems": true // No duplicate values
}
Object constraints:
{
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name"],
"additionalProperties": false, // Reject unknown keys
"minProperties": 1,
"maxProperties": 5
}
Combining schemas
allOf: Must match ALL of the subschemas:
{
"allOf": [
{"type": "object"},
{"required": ["name"]},
{"properties": {"name": {"minLength": 1}}}
]
}
anyOf: Must match AT LEAST ONE subschema:
{
"anyOf": [
{"type": "string"},
{"type": "number"}
]
}
oneOf: Must match EXACTLY ONE subschema:
{
"oneOf": [
{"properties": {"type": {"const": "circle"}}, "required": ["radius"]},
{"properties": {"type": {"const": "square"}}, "required": ["side"]}
]
}
if/then/else (draft-07+): Conditional validation:
{
"if": {
"properties": {"type": {"const": "business"}}
},
"then": {
"required": ["companyName", "vatNumber"]
},
"else": {
"required": ["firstName", "lastName"]
}
}
$ref and $defs: Reusable schemas
{
"$defs": {
"Address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"country": {"type": "string"}
},
"required": ["street", "city", "country"]
}
},
"type": "object",
"properties": {
"billingAddress": {"$ref": "#/$defs/Address"},
"shippingAddress": {"$ref": "#/$defs/Address"}
}
}
$defs (formerly definitions in draft-07) stores reusable schema fragments. $ref references them.
Validating with ajv (JavaScript)
npm install ajv
import Ajv from 'ajv';
import addFormats from 'ajv-formats'; // For "email", "uri", etc.
const ajv = new Ajv();
addFormats(ajv);
const schema = {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' }
}
};
const validate = ajv.compile(schema);
const data = { name: 'Alice', email: 'alice@example.com' };
if (validate(data)) {
console.log('Valid!');
} else {
console.log('Errors:', validate.errors);
}
Using ajv in Express.js for request validation:
app.post('/users', (req, res) => {
if (!validate(req.body)) {
return res.status(400).json({
errors: validate.errors.map(e => ({
field: e.instancePath,
message: e.message
}))
});
}
// Data is valid, proceed
});
Validating with jsonschema (Python)
pip install jsonschema
from jsonschema import validate, ValidationError
schema = {
"type": "object",
"required": ["name", "email"],
"properties": {
"name": {"type": "string", "minLength": 1},
"email": {"type": "string", "format": "email"}
}
}
try:
validate(instance={"name": "Alice", "email": "alice@example.com"}, schema=schema)
print("Valid!")
except ValidationError as e:
print(f"Invalid: {e.message}")
print(f"At path: {list(e.path)}")
Generating schemas from data
When you have sample data and need a schema:
Python:
from genson import SchemaBuilder
builder = SchemaBuilder()
builder.add_object({"name": "Alice", "age": 30, "active": True})
builder.add_object({"name": "Bob", "age": 25, "active": False, "tags": ["admin"]})
print(builder.to_json(indent=2))
Node.js: Use quicktype.io, which generates JSON Schema, TypeScript interfaces, and other types from JSON samples.
Generated schemas are starting points — add required, minLength, enum, and other constraints based on your business rules.
JSON Schema drafts
| Draft | Year | Key addition |
|---|---|---|
| Draft-04 | 2013 |
$schema, $ref, allOf/anyOf/oneOf
|
| Draft-07 | 2019 |
if/then/else, $comment, readOnly
|
| 2019-09 | 2019 |
$defs (replaces definitions), $anchor
|
| 2020-12 | 2020 |
prefixItems, $dynamicRef, unevaluatedProperties
|
ajv supports all drafts. Most tools use draft-07 — it's widely supported and has the features needed for most use cases.
JSON Schema validation is a cleanly defined, well-supported standard for data contracts. Using it at your API boundaries reduces bugs, improves error messages, and makes data expectations explicit in machine-readable form.
Top comments (0)