TL;DR
- JSON Schema = A contract for your JSON data
- Validates types, structure, formats, and constraints
- Catches bugs before they reach production
- Provides automatic documentation for your APIs
- Major validators: Ajv (JS), jsonschema (Python)
What is JSON Schema?
Imagine you're building an API. Users send you JSON data. But how do you know the data is correct? That the email field is actually an email? That age is a positive number?
You could write a bunch of if-statements. Or you could use JSON Schema — a standardized way to describe and validate JSON data.
Think of it like a blueprint. The schema says "this is what valid data looks like." The validator checks if incoming data matches that blueprint.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 },
"email": { "type": "string", "format": "email" }
},
"required": ["name", "email"]
} This schema says: "I expect an object with a string name, a non-negative integer age, and a properly formatted email. Name and email are required."
Why Use JSON Schema?
1. Catch Bugs Early
Invalid data gets rejected immediately, with clear error messages. No more debugging why your app crashed three layers deep.
2. Self-Documenting APIs
Your schema IS your documentation. Tools can generate API docs directly from schemas. OpenAPI/Swagger uses JSON Schema for exactly this.
3. Code Generation
Generate TypeScript types, form validation, and database models from a single schema. Write once, use everywhere.
4. Client-Side Validation
Validate user input in the browser before sending to the server. Better UX, fewer wasted API calls.
Basic Types
JSON Schema supports all JSON data types:
{
"stringField": { "type": "string" },
"numberField": { "type": "number" },
"integerField": { "type": "integer" },
"booleanField": { "type": "boolean" },
"nullField": { "type": "null" },
"arrayField": { "type": "array" },
"objectField": { "type": "object" }
} Multiple Types
A field can accept multiple types:
{
"id": { "type": ["string", "integer"] },
"middleName": { "type": ["string", "null"] }
} String Validation
Strings have powerful validation options:
{
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 3,
"maxLength": 20,
"pattern": "^[a-zA-Z0-9_]+$"
},
"email": {
"type": "string",
"format": "email"
},
"website": {
"type": "string",
"format": "uri"
},
"birthDate": {
"type": "string",
"format": "date"
},
"bio": {
"type": "string",
"minLength": 10,
"maxLength": 500
}
}
} Built-in Formats
| Format | Description | Example |
|---|---|---|
email | Email address | user@example.com |
uri | Full URI | https://example.com/path |
date | ISO 8601 date | 2024-11-25 |
date-time | ISO 8601 datetime | 2024-11-25T12:00:00Z |
uuid | UUID | 550e8400-e29b-41d4-... |
ipv4 | IPv4 address | 192.168.1.1 |
ipv6 | IPv6 address | ::1 |
Number Validation
{
"type": "object",
"properties": {
"age": {
"type": "integer",
"minimum": 0,
"maximum": 120
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": 0,
"multipleOf": 0.01
},
"quantity": {
"type": "integer",
"minimum": 1,
"maximum": 100
},
"rating": {
"type": "number",
"minimum": 0,
"maximum": 5,
"multipleOf": 0.5
}
}
} exclusiveMinimum and exclusiveMaximum
exclude the boundary value. So "minimum": 0, "exclusiveMinimum": true
means "greater than 0" (not including 0).
Array Validation
{
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
},
"scores": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 100
}
},
"matrix": {
"type": "array",
"items": {
"type": "array",
"items": { "type": "number" }
}
}
}
} Tuple Validation
For fixed-length arrays with specific types at each position:
{
"coordinates": {
"type": "array",
"prefixItems": [
{ "type": "number" },
{ "type": "number" }
],
"minItems": 2,
"maxItems": 2
}
} Object Validation
{
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"required": ["name", "email"],
"additionalProperties": false
} Key Options Explained
properties— Define the expected properties and their schemasrequired— Array of property names that must be presentadditionalProperties— Allow or disallow extra properties (false = strict)minProperties/maxProperties— Limit number of properties
Pattern Properties
Validate properties by pattern:
{
"type": "object",
"patternProperties": {
"^S_": { "type": "string" },
"^N_": { "type": "number" }
},
"additionalProperties": false
} This accepts {"S_name": "Alice", "N_age": 25} but rejects properties
that don't match either pattern.
Nested Objects
Real-world data is nested. Schemas handle this elegantly:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"profile": {
"type": "object",
"properties": {
"name": { "type": "string" },
"avatar": { "type": "string", "format": "uri" }
},
"required": ["name"]
},
"settings": {
"type": "object",
"properties": {
"theme": { "enum": ["light", "dark", "system"] },
"notifications": { "type": "boolean" }
}
}
},
"required": ["id", "profile"]
}
},
"required": ["user"]
} Enums and Constants
Enum (One of Many)
{
"status": {
"type": "string",
"enum": ["pending", "active", "completed", "cancelled"]
},
"priority": {
"type": "integer",
"enum": [1, 2, 3, 4, 5]
}
} Const (Exact Value)
{
"type": "object",
"properties": {
"version": { "const": "1.0.0" },
"type": { "const": "user" }
}
} Combining Schemas
allOf — Must Match All
{
"allOf": [
{ "type": "object", "properties": { "name": { "type": "string" } } },
{ "type": "object", "properties": { "age": { "type": "integer" } } }
]
} anyOf — Match At Least One
{
"anyOf": [
{ "type": "string" },
{ "type": "integer" }
]
} oneOf — Match Exactly One
{
"oneOf": [
{ "type": "object", "properties": { "type": { "const": "email" }, "address": { "type": "string" } } },
{ "type": "object", "properties": { "type": { "const": "phone" }, "number": { "type": "string" } } }
]
} Reusable Definitions ($defs)
Don't repeat yourself. Define schemas once, reference everywhere:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zipCode": { "type": "string", "pattern": "^[0-9]{5}$" }
},
"required": ["street", "city", "zipCode"]
}
},
"type": "object",
"properties": {
"billingAddress": { "$ref": "#/$defs/address" },
"shippingAddress": { "$ref": "#/$defs/address" }
}
} Conditional Validation
Different rules based on field values:
{
"type": "object",
"properties": {
"paymentMethod": { "enum": ["credit", "bank", "crypto"] }
},
"if": {
"properties": { "paymentMethod": { "const": "credit" } }
},
"then": {
"properties": {
"cardNumber": { "type": "string", "pattern": "^[0-9]{16}$" }
},
"required": ["cardNumber"]
},
"else": {
"if": {
"properties": { "paymentMethod": { "const": "bank" } }
},
"then": {
"properties": {
"accountNumber": { "type": "string" }
},
"required": ["accountNumber"]
}
}
} Validating in JavaScript
The most popular JavaScript validator is Ajv. It's fast, compliant, and well-maintained:
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
// Create validator instance
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
// Define schema
const schema = {
type: 'object',
properties: {
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0 }
},
required: ['name', 'email'],
additionalProperties: false
};
// Compile schema
const validate = ajv.compile(schema);
// Validate data
const data = {
name: 'Alice',
email: 'alice@example.com',
age: 25
};
if (validate(data)) {
console.log('Valid!');
} else {
console.log('Invalid:', validate.errors);
} Error Messages
const invalidData = {
name: '',
email: 'not-an-email',
age: -5,
extra: 'field'
};
if (!validate(invalidData)) {
validate.errors.forEach(error => {
console.log(`${error.instancePath} ${error.message}`);
});
}
// Output:
// /name must NOT have fewer than 1 characters
// /email must match format "email"
// /age must be >= 0
// must NOT have additional properties Real-World Schema Example
Here's a complete API request schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Create User Request",
"description": "Schema for user registration API",
"$defs": {
"email": {
"type": "string",
"format": "email",
"maxLength": 255
}
},
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 3,
"maxLength": 30,
"pattern": "^[a-zA-Z0-9_]+$",
"description": "Alphanumeric username"
},
"email": { "$ref": "#/$defs/email" },
"password": {
"type": "string",
"minLength": 8,
"maxLength": 128,
"description": "Must be at least 8 characters"
},
"profile": {
"type": "object",
"properties": {
"firstName": { "type": "string", "maxLength": 50 },
"lastName": { "type": "string", "maxLength": 50 },
"bio": { "type": "string", "maxLength": 500 }
}
},
"settings": {
"type": "object",
"properties": {
"newsletter": { "type": "boolean", "default": false },
"theme": { "enum": ["light", "dark"], "default": "light" }
},
"additionalProperties": false
}
},
"required": ["username", "email", "password"],
"additionalProperties": false
} Best Practices
- Start strict — Use
additionalProperties: falseto catch typos - Add descriptions — Document what each field is for
- Use $defs — Reuse common patterns like email, phone, address
- Validate on both sides — Client for UX, server for security
- Version your schemas — Breaking changes need careful handling
- Test your schemas — Write tests with valid and invalid data
Frequently Asked Questions
Which draft version should I use?
Use Draft 2020-12 for new projects. It's the latest stable version. Draft-07 is also widely supported if you need compatibility.
Can I generate TypeScript types from JSON Schema?
Yes! Use json-schema-to-typescript.
It converts schemas to TypeScript interfaces automatically.
How do I validate custom formats?
With Ajv, use ajv.addFormat() to define custom validation functions
for formats like phone numbers or custom IDs.
Continue Learning
- What is JSON? — Back to basics
- Common JSON Errors — Debug invalid JSON
- Working with JSON APIs — Apply schemas to APIs
- JSON Tools — Validate schemas online