JSON Schema Validation: A Practical Guide with Examples
JSON is flexible by design — any key, any value, any depth. That flexibility is great for prototyping and terrible for production APIs. JSON Schema is the standard way to describe what a valid JSON document must look like: which fields are required, what types they must be, and what constraints they must satisfy.
What JSON Schema actually is
A JSON Schema is itself a JSON document. You write it once, then use it to validate any number of input documents against it. The schema lives at $schema: "https://json-schema.org/draft/2020-12/schema" by convention. Libraries like AJV (Node.js), jsonschema (Python), and Newtonsoft.Json.Schema (.NET) implement validation against it.
A minimal schema looks like this:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": { "type": "integer", "minimum": 1 },
"name": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" }
},
"additionalProperties": false
}This schema says: the document must be an object with three required fields. id must be a positive integer, name a non-empty string, email a valid email address, and no extra fields are allowed.
The six core keywords
| Keyword | What it does | Example |
|---|---|---|
type | Constrains the JSON type | "type": "string" |
required | Lists mandatory keys in an object | "required": ["id"] |
properties | Defines sub-schemas for each key | "properties": { "age": { ... } } |
enum | Restricts value to a fixed set | "enum": ["active", "inactive"] |
minimum / maximum | Numeric range constraints | "minimum": 0, "maximum": 100 |
pattern | Regex the string must match | "pattern": "^[A-Z]{2}\d{4}$" |
String constraints
minLength/maxLength— character count boundspattern— a regex the value must satisfy (anchored to the full string)format— semantic hints:email,uri,date,date-time,uuid. Note: formats are annotations by default — validators only enforce them if you opt in (e.g.ajv.opts.formats)
Array constraints
items— the schema every element must satisfyminItems/maxItems— length boundsuniqueItems: true— no duplicates allowed
{
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}Composition keywords
JSON Schema has four composition keywords that let you build complex rules from simpler schemas:
allOf— must satisfy every listed schema (AND)anyOf— must satisfy at least one listed schema (OR)oneOf— must satisfy exactly one listed schema (XOR)not— must not satisfy the given schema
A common pattern is anyOf for nullable fields: "anyOf": [{ "type": "string" }, { "type": "null" }]. In JSON Schema 2019-09+ you can write this more concisely as "type": ["string", "null"].
Validating with AJV in Node.js
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv();
addFormats(ajv); // enables "email", "uri", "date-time" etc.
const schema = {
type: "object",
required: ["id", "name"],
properties: {
id: { type: "integer", minimum: 1 },
name: { type: "string", minLength: 1 },
},
additionalProperties: false,
};
const validate = ajv.compile(schema);
const valid = validate({ id: 1, name: "Alice" });
if (!valid) {
console.error(validate.errors);
}ajv.compile() returns a reusable validation function — compile once, call many times. The validate.errors array contains detailed error objects with the failing path, keyword, and message.
$ref and reusable definitions
Large schemas use $defs (formerly definitions) and $ref to avoid repetition:
{
"$defs": {
"Address": {
"type": "object",
"required": ["street", "city"],
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
}
}
},
"type": "object",
"properties": {
"billing": { "$ref": "#/$defs/Address" },
"shipping": { "$ref": "#/$defs/Address" }
}
}Common mistakes
- Forgetting
additionalProperties: false— without it, extra fields silently pass validation - Confusing
formatenforcement —"format": "email"is not enforced unless your validator is configured to check formats - Using
oneOfwhereanyOfis correct —oneOffails if more than one sub-schema matches, which is rarely what you want - Schema draft mismatch — AJV v8 defaults to Draft 2020-12; older tooling may use Draft 4 or 7 with different keyword semantics
Try it yourself
Use the free browser-based JSON Formatter & Validator on DevBench — no signup, runs entirely in your browser.
Open JSON Formatter & Validator