What Is JSON Schema? covers the vocabulary, and JSON Schema Examples is a reference for common shapes. This one is about the mechanics underneath both: how a validator actually walks a schema against a document, reuses schemas without repeating them, and handles rules that depend on the data itself.
Every applicable keyword is checked — nothing short-circuits
A schema isn't a sequence of steps that stops at the first failure the way an if chain would. A validator checks every keyword that applies to a value and collects whatever fails. Whether you see one error or all of them is a configuration choice, not a property of the schema itself — this is exactly the allErrors option this site's own validator runs with true, on a document missing two required fields at once:
// allErrors: false — stops after the first failure
["must have required property 'a'"]
// allErrors: true — reports everything wrong at once
["must have required property 'a'", "must have required property 'b'"]Neither is more "correct" — a fast-fail check for a hot code path might reasonably want the first option; a form that should show a user every problem on one submit wants the second.
Reusing schemas with $ref and $defs
Repeating the same sub-schema in multiple places is a maintenance trap — fix a constraint in one copy and forget the other. $defs holds reusable schema fragments, $ref points at one by JSON Pointer:
{
"type": "object",
"properties": {
"billing": { "$ref": "#/$defs/address" },
"shipping": { "$ref": "#/$defs/address" }
},
"$defs": {
"address": {
"type": "object",
"properties": { "city": { "type": "string" }, "zip": { "type": "string" } },
"required": ["city", "zip"]
}
}
}Both billing and shipping get the exact same validation rules from one definition — change address once and both update. This is also how OpenAPI documents structure reusable request/response shapes at scale, just with #/components/schemas/... as the pointer instead of #/$defs/....
Self-referencing schemas for recursive data
A $ref can point back at the schema it's already inside — necessary for anything tree-shaped, like a comment that can have replies which are themselves comments:
{
"type": "object",
"properties": {
"text": { "type": "string" },
"replies": { "type": "array", "items": { "$ref": "#" } }
},
"required": ["text"]
}# means "the root schema" — so each reply is validated against the exact same rules as the top-level comment, to whatever depth the data actually nests. No separate schema per nesting level, and no hardcoded recursion limit in the schema itself.
Conditional rules with if / then / else
Sometimes which fields are required depends on the value of another field — a classic case for a plain required array, which has no concept of "required only when." if/then/else covers it:
{
"type": "object",
"properties": { "businessType": { "enum": ["individual", "company"] } },
"required": ["businessType"],
"if": { "properties": { "businessType": { "const": "company" } } },
"then": { "required": ["taxId"] },
"else": { "required": ["ssn"] }
}{"businessType": "company", "taxId": "..."} passes; {"businessType": "company"} fails then's required check; {"businessType": "individual"} without ssn fails else's instead. if on its own asserts nothing — it's purely the condition that decides whether then or else applies.
Annotation keywords describe; they don't restrict
Not every keyword affects whether data passes. title, description, default, and examples are annotations — metadata for humans and tooling (an editor's tooltip, a generated form's placeholder) that a validator reports back but never enforces:
{
"type": "string",
"title": "Name",
"default": "Ada Lovelace",
"examples": ["Ada", "Grace"]
}Any string passes this schema — including one that matches neither the default nor either examples entry. Only type (and anything else that is an assertion — required, minLength, pattern, and the like) actually constrains the value. It's a common first assumption that examples works like enum; it doesn't — reach for enum if you actually mean to restrict the value to that list.
Try it yourself
JSON Schema Validator supports every keyword covered here, on both Draft-07 and 2020-12. Paste a schema using $ref, if/then/else, or annotations, and check its behavior against real data — entirely in your browser.