← Back to Blog

Understanding JSON Schema: Complete Validation Guide

Master JSON Schema for data validation. Learn schema syntax, validation techniques, and implementation across different programming languages.

Sarah Chen14 min readadvanced
S

Sarah Chen

Senior Software Engineer

Sarah is a full-stack software engineer with 8 years of experience in API development, TypeScript, and data engineering. She has designed and maintained large-scale JSON processing pipelines and contributes in-depth technical guides on performance optimisation, schema design, Python data workflows, and backend integration patterns.

TypeScriptAPI DevelopmentPythonData EngineeringJSON SchemaPerformance Tuning
14 min read

What is JSON Schema?

JSON Schema is a vocabulary for validating and describing JSON documents. It defines the structure, constraints, and validation rules for your JSON data.

Basic Schema Example

{

"$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"]

}

Schema Keywords

  • type: Data type (string, number, object, array, boolean, null)
  • required: Required properties
  • properties: Object property definitions
  • minimum/maximum: Number constraints
  • minLength/maxLength: String length constraints
  • pattern: Regex pattern for strings
  • format: Predefined formats (email, uri, date, etc.)

Validation in JavaScript

import Ajv from 'ajv';

const ajv = new Ajv();

const schema = {

type: 'object',

properties: {

name: { type: 'string' },

age: { type: 'number', minimum: 0 }

},

required: ['name']

};

const validate = ajv.compile(schema);

const valid = validate({ name: 'Alice', age: 30 });

if (!valid) {

console.log(validate.errors);

}

Validation in Python

from jsonschema import validate, ValidationError

schema = {

"type": "object",

"properties": {

"name": {"type": "string"},

"age": {"type": "integer", "minimum": 0}

},

"required": ["name"]

}

data = {"name": "Alice", "age": 30}

try:

validate(instance=data, schema=schema)

print("Valid!")

except ValidationError as e:

print(f"Error: {e.message}")

Advanced Features

Schema References ($ref)

Reuse schema definitions to keep schemas DRY:

{

"$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": {

"name": { "type": "string" },

"homeAddress": { "$ref": "#/$defs/address" },

"workAddress": { "$ref": "#/$defs/address" }

}

}

Schema Composition

allOf (AND) - Must match all schemas:
{

"allOf": [

{ "type": "object", "properties": { "name": { "type": "string" } } },

{ "type": "object", "properties": { "age": { "type": "number" } } }

]

}

// Valid: { "name": "Alice", "age": 30 }

anyOf (OR) - Must match at least one schema:
{

"anyOf": [

{ "type": "string" },

{ "type": "number" }

]

}

// Valid: "hello" or 42

oneOf (XOR) - Must match exactly one schema:
{

"oneOf": [

{ "type": "number", "multipleOf": 5 },

{ "type": "number", "multipleOf": 3 }

]

}

// Valid: 5, 15 (multiple of 5 OR 3, but not both)

// Invalid: 15 (multiple of BOTH 5 and 3)

Conditional Schemas (if/then/else)

{

"type": "object",

"properties": {

"country": { "type": "string" },

"postalCode": { "type": "string" }

},

"if": {

"properties": { "country": { "const": "US" } }

},

"then": {

"properties": {

"postalCode": { "pattern": "^[0-9]{5}(-[0-9]{4})?$" }

}

},

"else": {

"properties": {

"postalCode": { "type": "string" }

}

}

}

Real-World Schema Examples

API Request Validation

{

"$schema": "https://json-schema.org/draft/2020-12/schema",

"title": "Create User Request",

"type": "object",

"properties": {

"email": {

"type": "string",

"format": "email",

"description": "User's email address"

},

"password": {

"type": "string",

"minLength": 8,

"pattern": "^(?=.[A-Z])(?=.[a-z])(?=.[0-9])(?=.[!@#$%^&])",

"description": "Password must contain uppercase, lowercase, number, and special char"

},

"age": {

"type": "integer",

"minimum": 13,

"maximum": 120

},

"newsletter": {

"type": "boolean",

"default": false

}

},

"required": ["email", "password"],

"additionalProperties": false

}

Configuration File Validation

{

"type": "object",

"properties": {

"database": {

"type": "object",

"properties": {

"host": { "type": "string", "format": "hostname" },

"port": { "type": "integer", "minimum": 1, "maximum": 65535 },

"username": { "type": "string", "minLength": 1 },

"password": { "type": "string", "minLength": 8 },

"ssl": { "type": "boolean", "default": true }

},

"required": ["host", "port", "username", "password"]

},

"logging": {

"type": "object",

"properties": {

"level": {

"enum": ["debug", "info", "warn", "error"],

"default": "info"

},

"file": { "type": "string" }

}

}

},

"required": ["database"]

}

Schema Validation in Production

Express.js Middleware

import Ajv from 'ajv';

import addFormats from 'ajv-formats';

const ajv = new Ajv();

addFormats(ajv);

function validateSchema(schema) {

const validate = ajv.compile(schema);

return (req, res, next) => {

const valid = validate(req.body);

if (!valid) {

return res.status(400).json({

error: 'Validation failed',

details: validate.errors

});

}

next();

};

}

// Usage

const userSchema = { / schema / };

app.post('/api/users', validateSchema(userSchema), createUser);

FastAPI (Python) Automatic Validation

from fastapi import FastAPI

from pydantic import BaseModel, EmailStr, Field

class UserCreate(BaseModel):

email: EmailStr

password: str = Field(min_length=8, regex=r'^(?=.[A-Z])(?=.[a-z])(?=.[0-9])')

age: int = Field(ge=13, le=120)

newsletter: bool = False

app = FastAPI()

@app.post('/users')

async def create_user(user: UserCreate):

# user is automatically validated against schema

return {"email": user.email, "age": user.age}

Common Validation Patterns

Email Validation

{ "type": "string", "format": "email" }

URL Validation

{ "type": "string", "format": "uri" }

Date/Time Validation

{

"type": "string",

"format": "date-time" // ISO 8601: 2024-01-15T10:30:00Z

}

UUID Validation

{

"type": "string",

"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"

}

Phone Number Validation

{

"type": "string",

"pattern": "^\+?[1-9]\d{1,14}$" // E.164 format

}

Benefits of JSON Schema

  • Contract Enforcement: Ensures API clients send correct data
  • Documentation: Schema serves as machine-readable API documentation
  • Auto-generation: Generate TypeScript interfaces, validation code, test fixtures
  • Validation Anywhere: Same schema works in frontend (JS) + backend (Python, Go, etc.)
  • Error Messages: Detailed validation errors show exactly what's wrong
  • Best Practices

    1. Use $schema and $id

    {
    

    "$schema": "https://json-schema.org/draft/2020-12/schema",

    "$id": "https://example.com/schemas/user.json",

    "title": "User"

    }

    2. Provide Descriptions

    {
    

    "properties": {

    "timeout": {

    "type": "integer",

    "description": "Request timeout in milliseconds",

    "minimum": 0,

    "default": 5000

    }

    }

    }

    3. Use additionalProperties: false

    Prevent unexpected properties:

    {
    

    "type": "object",

    "properties": { "name": { "type": "string" } },

    "additionalProperties": false // Reject extra fields

    }

    4. Version Your Schemas

    When schemas evolve, version them:

    schemas/
    

    user-v1.json

    user-v2.json

    Conclusion

    JSON Schema is essential for building reliable APIs and data pipelines. It provides validation, documentation, and contract enforcement in one machine-readable format. Whether you're building REST APIs, processing user input, or validating configuration files, JSON Schema ensures data quality and prevents runtime errors.

    Key Takeaways:
    • Use schemas for all API endpoints
    • Validate early (at API boundary)
    • Share schemas between frontend/backend
    • Auto-generate TypeScript/types from schemas
    • Version schemas as they evolve
    • additionalProperties: Control extra properties

    Nested Schema Example

    {
    

    "type": "object",

    "properties": {

    "user": {

    "type": "object",

    "properties": {

    "name": { "type": "string" },

    "address": {

    "type": "object",

    "properties": {

    "street": { "type": "string" },

    "city": { "type": "string" }

    }

    }

    }

    }

    }

    }

    Array Validation

    {
    

    "type": "array",

    "items": {

    "type": "object",

    "properties": {

    "id": { "type": "integer" },

    "name": { "type": "string" }

    }

    },

    "minItems": 1,

    "maxItems": 100

    }

    Best Practices

  • Start simple, add complexity as needed
  • Use $defs for reusable schema parts
  • Provide clear error messages
  • Version your schemas
  • Test schemas thoroughly
  • Schema Reusability

    {
    

    "$defs": {

    "address": {

    "type": "object",

    "properties": {

    "street": { "type": "string" },

    "city": { "type": "string" }

    }

    }

    },

    "type": "object",

    "properties": {

    "billingAddress": { "$ref": "#/$defs/address" },

    "shippingAddress": { "$ref": "#/$defs/address" }

    }

    }

    Conclusion

    JSON Schema is essential for building robust APIs. It provides validation, documentation, and type safety for your JSON data!

    Share:

    Related Articles