What Is JSON Schema? JSON Validation, Explained
JSON Schema is a vocabulary for describing and validating the shape of JSON data. How schemas work, where they show up, and the keywords that matter.
JSON Schema is a standard vocabulary for describing what a JSON document is allowed to look like — which keys must exist, what types they hold, what values are acceptable. A schema is itself just a JSON document, and a validator checks other JSON against it, returning either “valid” or a precise list of everything that’s wrong. If JSON is the data format the web runs on, JSON Schema is the contract that keeps that data honest.
A schema in action
Say your API accepts a signup payload. Here is a schema that pins down its shape:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["email", "plan"],
"properties": {
"email": {
"type": "string",
"pattern": "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"
},
"plan": {
"type": "string",
"enum": ["free", "pro", "enterprise"]
},
"seats": {
"type": "integer",
"minimum": 1
}
},
"additionalProperties": false
}
This document passes:
{ "email": "morgan@example.com", "plan": "pro", "seats": 5 }
This one fails three ways — plan is missing, seats is below the minimum and the wrong type, and role isn’t an allowed key:
{ "email": "morgan@example.com", "seats": 0.5, "role": "admin" }
The point isn’t just the pass/fail verdict. A good validator reports each violation with its JSON path, so the client gets “seats must be an integer ≥ 1” instead of a mystery 400 error, and your handler code past the validation gate can trust the payload’s shape completely.
The keywords that matter
The vocabulary is large, but a dozen keywords cover the overwhelming majority of real schemas:
| Keyword | What it constrains |
|---|---|
type | JSON type: object, array, string, number, integer, boolean, null |
required | Object keys that must be present |
properties | A schema for each named key |
enum | An exact set of allowed values |
pattern | A regex the string must match |
minimum / maximum | Numeric bounds |
minLength / maxLength | String length bounds |
items | The schema every array element must match |
additionalProperties | Whether keys not listed in properties are allowed |
$ref | Reuse another schema by reference |
$ref is what makes schemas scale: define address or money once under $defs, reference it everywhere, and change it in one place.
Two more pieces of the vocabulary earn their keep in real APIs:
formatannotates strings with well-known shapes —"format": "email","date-time","uuid","uri". One caveat: the spec treatsformatas an annotation by default, so many validators ignore it unless you explicitly enable format checking. If your validation silently accepts"email": "not-an-email", this is usually why.- Composition keywords —
oneOf,anyOf,allOf,not— combine schemas. The workhorse isoneOffor discriminated unions: apaymentobject that must match exactly one of thecard,bank_transfer, orpaypalschemas, typically told apart by atypefield.
Where JSON Schema shows up
API boundaries. The classic use: validate every request body before your handler runs, so bad input fails fast at the edge instead of half-succeeding deep in business logic. The same applies to responses in contract tests — catching the moment a REST API starts returning a shape its consumers don’t expect.
OpenAPI. If you’ve used an API described by OpenAPI, you’ve used JSON Schema: OpenAPI describes the endpoints, methods, and auth, and delegates the request/response shapes to JSON Schema. Since OpenAPI 3.1 the two are fully aligned, so a schema written for one works in the other.
Editor autocomplete. When VS Code autocompletes keys in tsconfig.json or a CI workflow file, that’s a published JSON Schema at work — editors fetch the schema declared via the $schema key (or a public schema registry) and turn it into completions, hover docs, and inline red squiggles. Your own config files can get the same treatment by shipping a schema next to them.
Config validation. Because YAML parses into the same data structures as JSON, a JSON Schema validates YAML configs too — one schema can guard both a config.json and its YAML equivalent.
Form generation. A schema says “these fields, these types, these constraints” — which is enough for libraries to render a form, wire up its client-side validation, and keep it in sync with the server’s rules automatically.
Schemas as a source of truth for code
A schema is machine-readable, so tooling can go both directions:
- Schema → types. Generators emit TypeScript interfaces, Python dataclasses, or Go structs from a schema, so your compile-time types and runtime validation can’t drift apart.
- Types → schema. Frameworks derive schemas from your type or model definitions (Python’s Pydantic models are a well-known example), so you write the shape once and get validation, docs, and OpenAPI output for free.
- Compiled validators. Libraries like Ajv (JavaScript) compile a schema into a specialized validation function ahead of time, making per-request validation cheap enough to run on every call.
This is the quiet superpower: the schema stops being documentation about the system and becomes an artifact the system is generated from.
Using it well
A few habits separate pleasant schema use from painful:
- Validate at trust boundaries. Anything crossing from outside — API input, uploaded files, third-party webhooks, user-editable config — gets validated. Internal function-to-function data usually doesn’t need it.
- Close your input schemas. Set
additionalProperties: falseon request bodies so typos likeemialfail loudly instead of being silently ignored. - Keep schemas structural. “Discount can’t exceed 50% for free-plan users” is a business rule; encode it in code, not in increasingly baroque schema logic. Schemas are best at shape.
- Version schemas with the API. A schema change is an interface change. Review it like one.
The takeaway
JSON Schema turns “this endpoint expects roughly this kind of object” into an enforceable, machine-readable contract: a JSON document that validates other JSON, powers editor autocomplete, generates types and forms, and underpins OpenAPI. A handful of keywords — type, required, properties, enum, $ref — covers most real-world use. Validate at the boundaries, keep business rules out of it, and the schema becomes the single source of truth that your docs, types, and runtime checks all derive from.
Keep reading
The Lycoris Team · · 5 min read JSON vs YAML: Which Format Should You Use?
JSON and YAML represent the same data — the difference is syntax, strictness, and footguns. Where each format wins, and which to pick for configs and APIs.
The Lycoris Team · · 6 min read What Is JSON? The Data Format That Runs the Web
JSON is a lightweight text format for structured data — the default for APIs and config files. Syntax, examples, common mistakes, and why it beat XML.
Takina · · 4 min read What Is a Lockfile? Reproducible Dependency Installs
A lockfile records the exact dependency versions your package manager resolved, so every install — from your laptop to CI — reproduces the same tree.