JSON Schema: Validate and Document Your JSON Data
JSON Schema is a vocabulary for annotating and validating JSON documents. It describes the structure of your data — which fields are required, what types they must be, and what constraints they must satisfy. Think of it as TypeScript for your runtime data.
JSON Schema is used for:
- Validating API request/response bodies
- Documenting data structures in OpenAPI specs
- Generating forms from schema definitions
- Validating configuration files
- Code generation (TypeScript types from schema)
Basic Schema Structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user.json",
"title": "User",
"description": "A registered user in the system",
"type": "object"
}
| Keyword | Purpose |
|---|---|
$schema |
Which draft of JSON Schema to use |
$id |
Unique identifier for this schema |
title |
Human-readable name |
description |
Documentation |
type |
Data type constraint |
Primitive Types
{ "type": "string" }
{ "type": "number" }
{ "type": "integer" }
{ "type": "boolean" }
{ "type": "null" }
{ "type": ["string", "null"] }
String Constraints
{
"type": "string",
"minLength": 1,
"maxLength": 100,
"pattern": "^[a-zA-Z0-9_]+quot;,
"format": "email"
}
Format values (validation depends on the validator library):
| Format | Validates |
|---|---|
"email" |
Email addresses |
"uri" |
URIs |
"date" |
YYYY-MM-DD |
"time" |
HH:MM:SS |
"date-time" |
ISO 8601 datetime |
"uuid" |
UUID v1–v5 |
"ipv4" |
IPv4 address |
"ipv6" |
IPv6 address |
"hostname" |
DNS hostname |
Number Constraints
{
"type": "number",
"minimum": 0,
"maximum": 100,
"exclusiveMinimum": 0,
"multipleOf": 0.01
}
Objects
{
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Unique user ID"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"email": {
"type": "string",
"format": "email"
},
"role": {
"type": "string",
"enum": ["admin", "editor", "viewer"]
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": ["id", "name", "email"],
"additionalProperties": false
}
Key keywords:
properties: Defines the schema for each keyrequired: Array of keys that must be presentadditionalProperties: false: Reject extra fields not inpropertiesadditionalProperties: { "type": "string" }: Allow extra fields but constrain their type
Property Counts
{
"type": "object",
"minProperties": 1,
"maxProperties": 10
}
Arrays
{
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"maxItems": 100,
"uniqueItems": true
}
Tuple Validation (Fixed Length Array)
{
"type": "array",
"prefixItems": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" }
],
"items": false
}
This validates ["Alice", 30, true] but rejects ["Alice", 30, true, "extra"].
Nested Schemas
{
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": { "type": "string" },
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"country": { "type": "string", "minLength": 2, "maxLength": 2 }
},
"required": ["street", "city", "country"]
}
},
"required": ["name"]
},
"tags": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"uniqueItems": true
}
}
}
Reusing Schemas with $ref
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"Address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
},
"required": ["street", "city"]
},
"User": {
"type": "object",
"properties": {
"name": { "type": "string" },
"homeAddress": { "$ref": "#/$defs/Address" },
"workAddress": { "$ref": "#/$defs/Address" }
}
}
},
"$ref": "#/$defs/User"
}
$defs (formerly definitions) holds reusable subschemas. $ref references them by JSON Pointer.
Combining Schemas
// oneOf: exactly one must match
{
"oneOf": [
{ "type": "string" },
{ "type": "integer" }
]
}
// anyOf: one or more must match
{
"anyOf": [
{ "type": "string", "format": "email" },
{ "type": "string", "format": "uri" }
]
}
// allOf: all must match (useful for composition/inheritance)
{
"allOf": [
{ "$ref": "#/$defs/BaseUser" },
{
"properties": {
"adminLevel": { "type": "integer", "minimum": 1, "maximum": 3 }
},
"required": ["adminLevel"]
}
]
}
// not: must NOT match
{
"not": { "type": "string" }
}
Conditional Validation (if/then/else)
{
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["individual", "business"] },
"name": { "type": "string" },
"companyName": { "type": "string" },
"vatNumber": { "type": "string" }
},
"if": {
"properties": { "type": { "const": "business" } }
},
"then": {
"required": ["companyName", "vatNumber"]
},
"else": {
"required": ["name"]
}
}
Validating in JavaScript
AJV (Recommended)
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const ajv = new Ajv({ allErrors: true });
addFormats(ajv); // adds email, date-time, uri, etc.
const schema = {
type: 'object',
properties: {
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0, maximum: 150 },
},
required: ['name', 'email'],
additionalProperties: false,
};
const validate = ajv.compile(schema);
const data = { name: 'Alice', email: 'alice@example.com', age: 30 };
const valid = validate(data);
if (!valid) {
console.log(validate.errors);
// [{
// instancePath: '/email',
// message: 'must match format "email"',
// ...
// }]
}
Zod (TypeScript-first, more common in modern apps)
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(['admin', 'editor', 'viewer']),
});
type User = z.infer<typeof UserSchema>; // derives TypeScript type
const result = UserSchema.safeParse({ name: 'Alice', email: 'bad-email', role: 'admin' });
if (!result.success) {
result.error.issues.forEach(issue => {
console.log(issue.path, issue.message);
// ['email'] 'Invalid email'
});
}
Generating JSON Schema from Zod
import { zodToJsonSchema } from 'zod-to-json-schema';
const jsonSchema = zodToJsonSchema(UserSchema, 'User');
// produces standard JSON Schema from Zod definition
JSON Schema in OpenAPI
# openapi.yaml
paths:
/users:
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserInput'
components:
schemas:
CreateUserInput:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 100
email:
type: string
format: email
required: [name, email]
additionalProperties: false
→ Explore and validate JSON data structures in the browser with the JSON Viewer.