Understanding JSON Schema: Validation, Types, and Best Practices
JSON is everywhere - API payloads, configuration files, database documents, message queues. But how do you ensure that the JSON your application receives actually matches the structure you expect? A missing field, a wrong type, or an unexpected null can cascade into bugs that are difficult to trace. That is where JSON Schema comes in.
JSON Schema is a declarative language for describing the structure, constraints, and semantics of JSON data. Think of it as a contract: it defines what valid data looks like, and validators enforce that contract automatically. This guide walks through JSON Schema from the ground up, covering every type, the most useful validation keywords, reusable definitions, and common mistakes. If you want to generate a schema from existing JSON data, try our JSON Schema Generator.
What Is JSON Schema?
JSON Schema is itself a JSON document that describes another JSON document. It defines expected types, required properties, value constraints (minimum, maximum, pattern), and structural rules (nesting, arrays, enums). Here is a minimal 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"]
}
This schema says: the data must be an object with a name (string, required), an age (integer, at least 0, optional), and an email (string in email format, required). Any JSON object that does not match this description is invalid.
The $schema keyword declares which version of the specification this schema follows. Using it is optional but recommended - it tells validators which features are available.
Basic Types and Their Validation Keywords
JSON Schema defines seven types. Each has type-specific keywords for constraining values.
string
Strings are the most common type. Key validation keywords:
minLength/maxLength- constrain the number of characters.pattern- a regular expression the string must match. Example:"pattern": "^[A-Z]{2}\\d{4}$"requires two uppercase letters followed by four digits.format- a semantic hint like"email","uri","date","date-time","uuid", or"ipv4". Note that format validation is optional by default in many validators - you may need to enable it explicitly.enum- restricts the value to a fixed set:"enum": ["active", "inactive", "pending"].
{
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
}
number and integer
number allows any numeric value including decimals. integer restricts to whole numbers only. Key keywords:
minimum/maximum- inclusive bounds.exclusiveMinimum/exclusiveMaximum- exclusive bounds.multipleOf- value must be a multiple of this number. Useful for currency ("multipleOf": 0.01).
{
"type": "number",
"minimum": 0,
"maximum": 100,
"multipleOf": 0.01
}
boolean and null
boolean accepts only true or false. null accepts only null. These are simple types with no additional keywords. To allow a field to be either a string or null, use a type array: "type": ["string", "null"].
object
Objects are the workhorse of JSON Schema. Key keywords:
properties- defines schemas for each property by name.required- an array of property names that must be present.additionalProperties- controls whether properties not listed inpropertiesare allowed. Set tofalsefor strict validation or to a schema to constrain their type.minProperties/maxProperties- constrain the number of properties.patternProperties- define schemas for properties matching a regex pattern.
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string", "minLength": 1 },
"tags": { "type": "array", "items": { "type": "string" } }
},
"required": ["id", "name"],
"additionalProperties": false
}
array
Arrays hold ordered lists of values. Key keywords:
items- a schema that every item in the array must match.prefixItems(Draft 2020-12) - schemas for specific positions in a tuple. For example,[string, number]tuples.minItems/maxItems- constrain array length.uniqueItems- whentrue, all items must be distinct.contains- the array must contain at least one item matching this schema.
{
"type": "array",
"items": { "type": "string", "minLength": 1 },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
}
Nested Schemas and Composition
Real-world JSON is deeply nested. JSON Schema handles this naturally by nesting schema definitions:
{
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": { "type": "string" },
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^\\d{5}(-\\d{4})?$" }
},
"required": ["street", "city", "zip"]
}
},
"required": ["name"]
}
}
}
For more complex scenarios, JSON Schema provides composition keywords:
allOf- data must match all of the listed schemas (intersection).anyOf- data must match at least one schema (union).oneOf- data must match exactly one schema (exclusive or).not- data must not match the given schema.
These are powerful for modeling discriminated unions, polymorphic responses, and conditional validation.
Reusing Schemas with $ref and $defs
Duplicating schema definitions is a maintenance burden. The $ref keyword lets you reference a schema defined elsewhere. The convention is to define reusable schemas under $defs (called definitions in older drafts):
{
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"country": { "type": "string" }
},
"required": ["street", "city", "country"]
}
},
"type": "object",
"properties": {
"billing": { "$ref": "#/$defs/address" },
"shipping": { "$ref": "#/$defs/address" }
}
}
Both billing and shipping reference the same address schema. Change it once, and both references update. $ref can also point to external files ("$ref": "address-schema.json") for cross-file reuse in larger projects.
Common Mistakes and How to Avoid Them
1. Forgetting the required Array
Listing a property in properties does not make it required. You must also add its name to the required array. Without it, the property is optional and validation will pass even if it is missing.
2. Confusing type: "number" with type: "integer"
"type": "number" accepts 3.14 and 42. "type": "integer" accepts only 42. If you need whole numbers (IDs, counts, indices), use integer. If you need decimals (prices, measurements), use number.
3. Assuming format Is Validated by Default
The format keyword ("format": "email", "format": "uri") is treated as an annotation by default in Draft 2019-09 and later. Many validators do not enforce it unless you explicitly enable format validation. Always check your validator's documentation.
4. Using additionalProperties: false Too Aggressively
Setting additionalProperties: false makes your schema strict, but it also makes it brittle. If the producer adds a new field, consumers with this setting will reject the data. Use it for internal schemas where you control both sides, but consider being lenient for public APIs.
5. Circular References Without a Base Case
Schemas can reference themselves for recursive data structures (like trees), but every recursive path must have a termination condition. Without it, validators will loop infinitely or crash.
Tools and Validators
Popular JSON Schema validators by language:
- JavaScript/TypeScript: Ajv - the fastest JSON Schema validator for Node.js and browsers.
- Python:
jsonschema- the reference implementation for Python. - .NET:
Newtonsoft.Json.Schema- reliable validation with detailed error messages. - Java:
everit-org/json-schema- Draft 7 and 2020-12 support. - Go:
santhosh-tekuri/jsonschema- high-performance validator.
For quick experimentation, our JSON Schema Generator creates a schema from any JSON you paste, and our JSON Validator checks data against a schema right in your browser.
Generate and Validate Your Schemas
Whether you are building a new API, documenting an existing one, or validating configuration files, JSON Schema gives you a machine-readable contract that catches errors before they reach production:
- JSON Schema Generator - paste JSON and get a complete schema instantly.
- JSON Formatter & Validator - format and validate any JSON document.
- JSON Validator - validate JSON against a schema definition.