JSON's flexibility is both its greatest strength and its greatest weakness. Any valid JSON can represent any data structure — but that means there's nothing preventing an API consumer from sending a user object without a name field, an age value of "twenty-eight" instead of 28, or an email address that's actually a phone number. Without validation, these malformed inputs silently propagate through your system, causing bugs that surface far from where the bad data entered.
JSON Schema solves this problem by providing a standardized way to describe what valid JSON data should look like. It's a vocabulary for writing validation rules — specifying which properties are required, what types they must be, what values they can contain, and how nested structures should be organized. JSON Schema is written in JSON itself, making it both human-readable and machine-processable. It serves triple duty as a validation tool, a documentation format, and a contract between services in a microservice architecture.
This guide provides a practical, example-driven introduction to JSON Schema validation. We'll start with basic type and property validation, progress through advanced features like conditional schemas and composition operators, and show you how to integrate schema validation into your APIs, tests, and CI/CD pipelines. Every example uses real-world patterns that you can copy directly into your projects.
Validate JSON Structure Instantly
Our JSON Formatter validates syntax and structure in real-time — paste any JSON to see immediate error feedback.
Open JSON Formatter →Your First JSON Schema
A JSON Schema is itself a JSON object that describes the expected structure of your data. Here's a simple example that validates a user object:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"email": {
"type": "string",
"format": "email"
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"isActive": {
"type": "boolean"
}
},
"required": ["name", "email"],
"additionalProperties": false
}This schema enforces: the root must be an object, name must be a non-empty string (max 100 characters), email must be a valid email format, age must be an integer between 0 and 150, isActive must be boolean, name and email are required (others optional), and no unexpected properties are allowed.
Core Validation Keywords
| Keyword | Applies To | Description | Example |
|---|---|---|---|
type |
Any | Required data type | "type": "string" |
required |
Object | List of required property names | "required": ["name"] |
enum |
Any | Allowed values list | "enum": ["active", "inactive"] |
minimum / maximum |
Number | Value range constraints | "minimum": 0, "maximum": 100 |
minLength / maxLength |
String | Character count limits | "minLength": 1, "maxLength": 255 |
pattern |
String | Regex validation | "pattern": "^[A-Z]{2}\\d{4}$" |
format |
String | Semantic format (email, uri, date) | "format": "email" |
items |
Array | Schema for array elements | "items": {"type": "string"} |
minItems / maxItems |
Array | Array length constraints | "minItems": 1, "maxItems": 10 |
uniqueItems |
Array | All elements must be unique | "uniqueItems": true |
additionalProperties |
Object | Allow/deny undefined properties | "additionalProperties": false |
default |
Any | Default value (informational) | "default": "active" |
Validating Nested Objects
Real-world data is rarely flat. JSON Schema supports deep nesting through recursive property definitions. Each nested object can have its own type constraints, required fields, and additional properties rules:
{
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": { "type": "string" },
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zipCode": {
"type": "string",
"pattern": "^\\d{5}(-\\d{4})?$"
}
},
"required": ["street", "city", "zipCode"]
}
},
"required": ["name", "address"]
}
}
}Validating Arrays
Arrays require their own set of validation keywords. You can validate the type and structure of each element, constrain the array length, and ensure uniqueness:
{
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
},
"scores": {
"type": "array",
"items": {
"type": "number",
"minimum": 0,
"maximum": 100
}
}
}
}Composition: allOf, anyOf, oneOf
JSON Schema provides composition keywords for combining multiple schemas into complex validation rules. These are essential for representing real-world data that has conditional or variant structures:
| Keyword | Meaning | Use Case |
|---|---|---|
allOf |
Must match ALL schemas | Combining base schemas with extensions |
anyOf |
Must match AT LEAST ONE | Multiple valid formats for a field |
oneOf |
Must match EXACTLY ONE | Discriminated unions / variant types |
not |
Must NOT match | Excluding specific patterns |
Reusable Schemas with $ref
As schemas grow complex, you'll want to define sub-schemas once and reference them throughout your schema. The $ref keyword enables this via JSON Pointers. Define reusable schemas in $defs (or definitions in Draft-07) and reference them anywhere within the same document, or even across different files over the network:
{
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"country": { "type": "string", "maxLength": 2, "description": "ISO 3166-1 alpha-2 code" }
},
"required": ["street", "city", "country"]
}
},
"type": "object",
"properties": {
"homeAddress": { "$ref": "#/$defs/address" },
"workAddress": { "$ref": "#/$defs/address" },
"shippingAddress": { "$ref": "https://example.com/schemas/common/address.json" }
}
}Note: When using external references (URLs), your validation library must be configured to fetch and resolve remote schemas, which has security and performance implications.
Advanced Conditional Logic: If / Then / Else
One of the most powerful features introduced in Draft-07 is the ability to apply validation rules conditionally based on the presence or value of other fields. This is essential for modeling complex business rules directly in the schema.
For example, if a user selects "United States" as their country, the "state" field should be required and must be a 2-letter code. If they select any other country, "state" is optional but if provided, must be a string.
{
"type": "object",
"properties": {
"country": { "type": "string" },
"postalOverride": { "type": "string" }
},
"if": {
"properties": { "country": { "const": "US" } },
"required": ["country"]
},
"then": {
"properties": {
"state": { "type": "string", "pattern": "^[A-Z]{2}$" }
},
"required": ["state"]
},
"else": {
"properties": {
"state": { "type": "string" }
}
}
}Metadata, Annotations, and Documentation Generation
JSON Schema isn't just for validation; it's a documentation format natively supported by tools like Swagger/OpenAPI. By enriching your schemas with metadata keywords, you guarantee that your validation logic and your API documentation never drift out of sync.
title: A short, human-readable name for the schema.description: A detailed explanation of the purpose or constraints of the data.examples: An array of valid sample data used by documentation generators and mock servers.deprecated: A boolean flag (Draft 2019-09+) indicating that a property should no longer be used.readOnly/writeOnly: Highlights whether a property will be ignored during creation (readOnly) or hidden during retrieval (writeOnly).
{
"properties": {
"passwordHash": {
"type": "string",
"writeOnly": true,
"description": "Bcrypt hash. Never returned in API responses."
},
"legacyId": {
"type": "integer",
"deprecated": true,
"description": "Use the uuid field instead."
}
}
}Integration: API Gateways and Middleware
Validation should happen as early as possible in the request lifecycle. If malformed data reaches your database layer, it's too late. The best practice is to implement JSON Schema validation at the API Gateway or routing middleware level, before the request ever reaches your application controllers.
API Gateways: AWS API Gateway, Kong, and Tyk all native support JSON Schema validation. If you configure a schema on a route, the gateway will reject invalid requests with a 400 Bad Request before your lambda or backend service is even invoked, saving massive compute costs on bogus traffic.
Express.js Middleware:
import Ajv from "ajv";
import userSchema from "./schemas/user.json";
const ajv = new Ajv({ allErrors: true, removeAdditional: true });
const validateUser = ajv.compile(userSchema);
export function validateSchema(req, res, next) {
const valid = validateUser(req.body);
if (!valid) {
return res.status(400).json({
error: "Validation Failed",
details: validateUser.errors
});
}
next();
}
// Attach to route
app.post("/users", validateSchema, userController.create);Testing Methodologies: Validating the Validator
A JSON Schema is code, and like any code, it must be tested. A subtle regex mistake in a pattern keyword could inadvertently block legitimate users (e.g., blocking valid email formats). You must create unit tests for your schemas.
The industry standard approach is a test suite structured as an array of test cases: a JSON object containing the schema, an array of valid test inputs that should pass, and an array of invalid test inputs (with edge cases) that should fail. Your CI/CD pipeline should iterate through these inputs and assert the exact output of the ajv.validate() boolean.
Performance Optimization and Caching
Schema validation is computationally expensive, especially for large nested arrays. To achieve high throughput (thousands of requests per second), you must adhere to strict performance rules:
- Never compile on every request. Compiling a schema (translating the JSON rules into executable code) is slow. Always compile the schema exactly once during application startup (e.g.,
ajv.compile()) and reuse the resulting validation function in memory. - Use
discriminatorforoneOfandanyOf. When validating polymorphic arrays (where objects can be of different types), standardoneOfrequires the engine to evaluate every sub-schema until one passes. By using the OpenAPIdiscriminatorkeyword, the engine can instantly lookup the correct sub-schema based on a specific property (like"type": "admin"), transforming an O(N) operation into an O(1) operation. - Strip additional properties at the schema level. Instead of writing manual sanitation code, configure your validator (like Ajv's
removeAdditional: true) to aggressively drop any properties not explicitly defined in the schema before passing the payload to the application.
Validation Libraries by Language Ecosystem
Every major language has an implementation of the JSON Schema standard. When selecting a library, evaluate its support for the latest Drafts, its ability to output standard RFC 7807 error logs, and its compilation speed.
| Language | Library | Draft Support | Speed Profile |
|---|---|---|---|
| JavaScript/Node.js | Ajv | Draft-07, 2019-09, 2020-12 | JIT Compiled (Fastest) |
| Python | jsonschema | Draft-04 through 2020-12 | Interpreted (Fast) |
| Python (High Perf) | fastjsonschema | Draft-04 through Draft-07 | Code Generated (Very Fast) |
| Java | networknt/json-schema-validator | Draft-04 through 2020-12 | Compiled (Fast) |
| Go | santhosh-tekuri/jsonschema | Draft-04 through 2020-12 | Compiled (Very Fast) |
| PHP | justinrainbow/json-schema | Draft-03, Draft-04 | Interpreted (Moderate) |
| Ruby | json_schemer | Draft-04, Draft-06, Draft-07 | Interpreted (Moderate) |
Check Your JSON Structure
Validate any JSON data instantly — our formatter catches syntax and structural issues before they become bugs.
Open JSON Formatter →Frequently Asked Questions
What is JSON Schema?
Why should I use JSON Schema validation?
What is the latest JSON Schema version?
$dynamicRef.
How do I validate JSON against a schema in JavaScript?
npm install ajv ajv-formats. Compile the schema with ajv.compile(schema), call validate(data), and check validate.errors for detailed validation messages.
Can JSON Schema validate nested objects and arrays?
properties for nested objects, items for arrays, minItems/maxItems for array length, and $ref for reusable sub-schemas. Combine with allOf/anyOf/oneOf for complex logic.
Related Resources
- JSON to CSV Converter — Try it free on DominateTools
- JSON-LD Schema Generator — Try it free on DominateTools
- JSON Syntax Guide — Complete JSON reference
- JSON vs XML vs YAML — Format comparison
- Debug JSON API Responses — API troubleshooting guide
- JSON Best Practices — Design and performance tips
- Free JSON Formatter — Validate JSON instantly