When APIs exchange JSON data, things can go wrong fast — missing fields, wrong types, unexpected values. JSON Schema provides a standardised way to define and validate the structure of JSON documents, catching errors before they cause bugs downstream.
What Is JSON Schema?
JSON Schema is a vocabulary that allows you to describe the shape of your JSON data. Think of it as a contract: "this field must be a string", "this number must be between 0 and 100", "this array must have at least one item". It's written in JSON itself, which means it integrates naturally into any JSON-based workflow.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 1 },
"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 (non-empty string), an optional age (non-negative integer), and a required email in valid format.
Core Keywords
JSON Schema uses keywords to define constraints. Here are the most important ones:
Type Constraints
{ "type": "string" } // Must be a string
{ "type": "number" } // Any number (integer or float)
{ "type": "integer" } // Whole numbers only
{ "type": "boolean" } // true or false
{ "type": "array" } // Must be an array
{ "type": "object" } // Must be an object
{ "type": "null" } // Must be null
{ "type": ["string", "null"] } // String OR null
String Validation
{
"type": "string",
"minLength": 1, // At least 1 character
"maxLength": 255, // At most 255 characters
"pattern": "^[A-Z]{2,3}$" // Regex: 2-3 uppercase letters
}
Number Validation
{
"type": "number",
"minimum": 0, // >= 0
"maximum": 100, // <= 100
"exclusiveMinimum": 0, // > 0 (not equal)
"multipleOf": 0.01 // Must be a multiple of 0.01 (2 decimals)
}
Array Validation
{
"type": "array",
"items": { "type": "string" }, // Each item must be a string
"minItems": 1, // At least 1 item
"maxItems": 10, // At most 10 items
"uniqueItems": true // No duplicates
}
Required Fields & Defaults
The required keyword lists which properties must be present:
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"role": { "type": "string", "default": "user" }
},
"required": ["id", "name"]
}
Here, id and name are mandatory. role is optional and defaults to "user" if omitted (though the default is advisory — your application code must apply it).
Nested Objects & References
Real-world data is nested. JSON Schema handles this naturally:
{
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": { "type": "string" },
"address": { "$ref": "#/$defs/address" }
},
"required": ["name"]
}
},
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^[0-9]{5,6}$" }
},
"required": ["street", "city"]
}
}
}
The $ref keyword references a reusable definition from $defs, keeping your schema DRY.
Conditional Validation
JSON Schema supports if/then/else for conditional rules:
{
"type": "object",
"properties": {
"payment_type": { "enum": ["card", "upi", "cash"] }
},
"if": {
"properties": { "payment_type": { "const": "card" } }
},
"then": {
"properties": {
"card_number": { "type": "string", "pattern": "^[0-9]{16}$" }
},
"required": ["card_number"]
}
}
If payment_type is "card", then card_number is required and must be 16 digits.
Composition Keywords
Combine multiple schemas with logical operators:
- allOf — Must match ALL schemas (intersection)
- anyOf — Must match at least ONE schema (union)
- oneOf — Must match EXACTLY one schema (exclusive OR)
- not — Must NOT match the schema
{
"oneOf": [
{ "type": "string", "maxLength": 5 },
{ "type": "integer", "minimum": 0 }
]
}
// Valid: "hello" or 42
// Invalid: "toolong" or -1
Popular Validation Libraries
| Language | Library | Draft Support |
|---|---|---|
| JavaScript | ajv | 2020-12, 2019-09, Draft-07 |
| Python | jsonschema | 2020-12, 2019-09, Draft-07 |
| Java | networknt/json-schema-validator | 2020-12, Draft-07 |
| Go | santhosh-tekuri/jsonschema | 2020-12 |
| PHP | opis/json-schema | 2020-12 |
JavaScript example with Ajv
import Ajv from "ajv";
const ajv = new Ajv();
const schema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "integer", minimum: 0 }
},
required: ["name"],
additionalProperties: false
};
const validate = ajv.compile(schema);
const valid = validate({ name: "Rahul", age: 28 });
if (!valid) console.log(validate.errors);
Best Practices
- Use
additionalProperties: falsein strict APIs to reject unknown fields - Set
"$schema"at the top to declare the draft version - Use
$defsand$reffor reusable definitions — avoid copy-pasting schemas - Add
"format"for strings like emails, dates, URIs (note: format validation is optional by default in most libraries) - Test edge cases: empty strings, null values, empty arrays, and boundary numbers
- Version your schemas: include a version field or use separate schema files per API version
Quick Reference
type → string, number, integer, boolean, array, object, null
enum → exact allowed values: ["red","green","blue"]
const → single exact value: "active"
pattern → regex for strings: "^[a-z]+$"
minimum/maximum → number bounds
minLength/maxLength → string length bounds
minItems/maxItems → array size bounds
required → list of mandatory property names
$ref → reference another schema definition
if/then/else → conditional validation
allOf/anyOf/oneOf → schema composition