- JSON Schema Validator — validate JSON against any schema in-browser
- JSON → JSON Schema Generator — auto-generate a schema from a JSON example
- JSON Schema → TypeScript Generator — convert schemas to TypeScript interfaces
1. What is JSON Schema?
JSON Schema is a declarative vocabulary for describing the structure and constraints of JSON data. A JSON Schema is itself a JSON document that defines what another JSON document should look like — what properties it must have, what types they must be, what values are allowed, and which fields are required.
JSON Schema solves a real problem in API-first development: JSON is completely permissive by nature. Any key, any value, any nesting depth is valid JSON. Without a schema, there is no machine-readable contract between the producer and consumer of an API. JSON Schema provides that contract.
What JSON Schema Is Used For
- API request validation — reject malformed requests at the gateway before they reach your business logic.
- API response validation — ensure your API always returns what it promises (contract testing).
- Form validation — libraries like AJV validate form inputs server-side in milliseconds.
- Configuration file validation — VS Code uses JSON Schema for IntelliSense and validation in
tsconfig.json,package.json, and similar files. - TypeScript type generation — convert schemas to TypeScript interfaces automatically, keeping runtime and compile-time types in sync.
- Documentation — OpenAPI 3.x uses JSON Schema as its schema language for documenting API shapes.
2. Draft Versions: Which to Use?
JSON Schema has evolved through several draft versions. Use Draft 2020-12 for new projects:
| Version | $schema URI | Status | Key Changes |
|---|---|---|---|
| Draft 4 | http://json-schema.org/draft-04/schema# | Legacy | Foundation; definitions, allOf/anyOf/oneOf |
| Draft 6 | http://json-schema.org/draft-06/schema# | Legacy | const, contains, propertyNames |
| Draft 7 | http://json-schema.org/draft-07/schema# | Widely used | if/then/else, readOnly/writeOnly |
| Draft 2019-09 | https://json-schema.org/draft/2019-09/schema | Stable | $defs replaces definitions, $recursiveRef |
| Draft 2020-12 | https://json-schema.org/draft/2020-12/schema | ✅ Current | prefixItems, $dynamicRef, unevaluatedProperties |
$schema at the top of your schema tells validators which draft to use. Always include it. The most widely supported combination today is Draft 7 (AJV default) or 2020-12 (AJV with ajv/dist/2020). Check your validator's supported drafts before choosing.
3. Basic Types and Type-Specific Keywords
JSON Schema defines 7 primitive types, mapping directly to JSON's native types:
| Type | JSON Example | Key Validation Keywords |
|---|---|---|
string | "hello" | minLength, maxLength, pattern, format, enum, const |
number | 3.14 | minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf |
integer | 42 | Same as number; value must have no fractional part |
boolean | true | const: true / const: false |
null | null | Only matches JSON null |
object | {"key": "val"} | properties, required, additionalProperties, minProperties, maxProperties, patternProperties |
array | [1, 2, 3] | items, prefixItems, contains, minItems, maxItems, uniqueItems |
Nullable Types
A field can accept multiple types by passing an array to type:
// Nullable string (string or null):
{ "type": ["string", "null"] }
// In Draft 2020-12, you can also use anyOf:
{ "anyOf": [{ "type": "string" }, { "type": "null" }] } enum and const
// enum: value must be one of these exactly
{ "type": "string", "enum": ["pending", "active", "deleted"] }
// const: value must be exactly this (a single-value enum)
{ "const": "active" } 4. Object Validation: properties, required, additionalProperties
Here is a complete annotated user schema demonstrating all major object keywords:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user.json",
"title": "User",
"description": "A registered user of the application",
"type": "object",
"required": ["id", "email", "createdAt"],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique user identifier"
},
"email": {
"type": "string",
"format": "email",
"maxLength": 320
},
"name": {
"type": ["string", "null"],
"minLength": 1,
"maxLength": 100
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"role": {
"type": "string",
"enum": ["admin", "user", "moderator"]
},
"tags": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"maxItems": 20
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"additionalProperties": false
} additionalProperties: false — Strict Mode
By default, JSON Schema allows objects to have any properties beyond those declared in properties. Setting additionalProperties: false makes the schema strict — extra properties cause validation failure. This is equivalent to TypeScript's exact object type checking and is critical for catching typos in property names.
patternProperties — Property Name Patterns
{
"type": "object",
"patternProperties": {
"^x-": { "type": "string" } // any property starting with x- must be a string
},
"additionalProperties": false
} unevaluatedProperties (Draft 2020-12)
unevaluatedProperties is like additionalProperties but it understands composition keywords (allOf, if/then/else). Use it when combining schemas with allOf where each schema declares some properties — additionalProperties: false on the combined schema would incorrectly reject properties from the sub-schemas.
5. Array Validation: items, prefixItems, contains
// Homogeneous array: all items must match the same schema
{
"type": "array",
"items": { "type": "string", "format": "email" },
"minItems": 1,
"maxItems": 100,
"uniqueItems": true // no duplicates
}
// Tuple (Draft 2020-12): each position has a specific type
// (was 'items' as array in older drafts)
{
"type": "array",
"prefixItems": [
{ "type": "string" }, // position 0: string
{ "type": "number" }, // position 1: number
{ "type": "boolean" } // position 2: boolean
],
"items": false // no additional items beyond the 3 defined
}
// contains: at least one item must match
{
"type": "array",
"contains": { "type": "string", "format": "email" },
"minContains": 1, // at least 1 email required
"maxContains": 5 // at most 5 emails
} 6. $ref and $defs: Reusable Schemas
$ref is JSON Schema's equivalent of TypeScript type aliases. Instead of repeating a complex schema in multiple places, define it once in $defs (or definitions in older drafts) and reference it:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/order.json",
"title": "Order",
"type": "object",
"required": ["orderId", "customer", "items", "total"],
"properties": {
"orderId": { "type": "string", "format": "uuid" },
"customer": { "$ref": "#/$defs/Customer" },
"items": {
"type": "array",
"items": { "$ref": "#/$defs/LineItem" },
"minItems": 1
},
"total": { "$ref": "#/$defs/Money" },
"status": {
"type": "string",
"enum": ["pending", "processing", "shipped", "delivered", "cancelled"]
}
},
"$defs": {
"Customer": {
"type": "object",
"required": ["id", "email"],
"properties": {
"id": { "type": "string" },
"email": { "type": "string", "format": "email" },
"name": { "type": "string" }
}
},
"LineItem": {
"type": "object",
"required": ["productId", "quantity", "unitPrice"],
"properties": {
"productId": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 },
"unitPrice": { "$ref": "#/$defs/Money" }
}
},
"Money": {
"type": "object",
"required": ["amount", "currency"],
"properties": {
"amount": { "type": "number", "minimum": 0 },
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
}
}
}
} External $ref
$ref can point to an external schema file or URL:
// Reference an external schema file:
{ "$ref": "https://example.com/schemas/address.json" }
// Reference another local file:
{ "$ref": "./address.schema.json" }
// Reference a specific definition in another file:
{ "$ref": "./types.schema.json#/$defs/Money" } 7. Schema Composition: allOf, anyOf, oneOf, not
// allOf — must match ALL schemas (intersection, like TypeScript &)
{
"allOf": [
{ "$ref": "#/$defs/BaseEntity" },
{ "$ref": "#/$defs/HasTimestamps" },
{
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" }
}
}
]
}
// anyOf — must match AT LEAST ONE schema (union, like TypeScript |)
{
"anyOf": [
{ "type": "string", "format": "email" },
{ "type": "string", "format": "uri" }
]
}
// oneOf — must match EXACTLY ONE schema (exclusive union)
{
"oneOf": [
{
"type": "object",
"properties": { "type": { "const": "card" }, "cardNumber": { "type": "string" } },
"required": ["type", "cardNumber"]
},
{
"type": "object",
"properties": { "type": { "const": "paypal" }, "paypalEmail": { "type": "string" } },
"required": ["type", "paypalEmail"]
}
]
}
// not — must NOT match the schema
{
"not": { "type": "null" }
} | Keyword | Logic | TypeScript Equivalent | Use Case |
|---|---|---|---|
| allOf | AND — all must pass | A & B | Extending base schemas, mixins |
| anyOf | OR — at least one must pass | A | B | Union types, flexible input |
| oneOf | XOR — exactly one must pass | Discriminated union | Mutually exclusive variants |
| not | NOT — must not match | Exclude<T, U> | Blacklisting values or types |
Discriminated Unions with oneOf
The payment method example in the code above is a discriminated union — a common API pattern where a type field determines which properties are required. JSON Schema handles this cleanly with oneOf + const. Tools like AJV validate these efficiently using the discriminator field.
8. Conditional Schemas: if/then/else
Draft 7+ supports conditional validation: if one condition passes, apply one set of rules; otherwise apply another. This avoids duplicating schemas in multiple oneOf branches when only a few fields differ:
// if/then/else — conditional validation
{
"type": "object",
"properties": {
"paymentMethod": { "type": "string" },
"cardNumber": { "type": "string" },
"bankAccountNumber": { "type": "string" }
},
"if": {
"properties": { "paymentMethod": { "const": "credit_card" } },
"required": ["paymentMethod"]
},
"then": {
"required": ["cardNumber"]
},
"else": {
"required": ["bankAccountNumber"]
}
} dependentRequired and dependentSchemas
// dependentRequired: if 'cardNumber' is present, 'cvv' is also required
{
"dependentRequired": {
"cardNumber": ["cvv", "expiryMonth", "expiryYear"]
}
}
// dependentSchemas: if 'type' is present, apply additional schema
{
"dependentSchemas": {
"creditCard": {
"properties": { "creditLimit": { "type": "number" } },
"required": ["creditLimit"]
}
}
} 9. String Formats
The format keyword provides semantic validation for strings. Note: in JSON Schema, format validation is optional — validators may or may not enforce it by default. AJV requires explicit opt-in with { "formats": "full" }.
| Format | Description | Example |
|---|---|---|
date-time | RFC 3339 datetime | 2026-06-19T10:30:00Z |
date | ISO 8601 date | 2026-06-19 |
time | ISO 8601 time | 10:30:00 |
duration | ISO 8601 duration | P1Y2M3DT4H |
email | Email address | [email protected] |
idn-email | Internationalized email | 用户@例子.广告 |
uri | URI | https://example.com/path |
uri-reference | URI or relative reference | /relative/path |
uuid | UUID v1–v5 | 550e8400-e29b-41d4-a716-446655440000 |
ipv4 | IPv4 address | 192.168.1.1 |
ipv6 | IPv6 address | 2001:db8::1 |
hostname | DNS hostname | api.example.com |
json-pointer | JSON Pointer | /properties/name |
10. Annotations: title, description, default, examples
Annotation keywords do not affect validation — they add human-readable documentation and tooling hints:
{
"type": "object",
"title": "Product",
"description": "A product in the catalog",
"properties": {
"price": {
"type": "number",
"title": "Price",
"description": "Price in USD, inclusive of tax",
"default": 0,
"examples": [9.99, 24.99, 99.0],
"minimum": 0
},
"sku": {
"type": "string",
"title": "SKU",
"description": "Stock keeping unit identifier",
"pattern": "^[A-Z]{2}-[0-9]{6}$",
"examples": ["AB-123456"]
}
}
} OpenAPI 3.x, VS Code IntelliSense, Swagger UI, and documentation generators all consume these annotations to build human-readable docs from your schema — no manual documentation needed.
11. Generating TypeScript from JSON Schema
One of the most powerful uses of JSON Schema is automatic TypeScript type generation. This keeps your runtime validation (AJV schema) and compile-time types (TypeScript interfaces) perfectly in sync — a single source of truth.
// Input JSON Schema (simplified):
// { type: "object", properties: { id: {type:"string"}, age: {type:"integer"} } }
// Generated TypeScript (from json-schema-to-typescript or our tool):
export interface User {
id: string;
email: string;
name?: string | null;
age?: number;
role?: "admin" | "user" | "moderator";
tags?: string[];
createdAt: string;
[k: string]: unknown; // if additionalProperties not false
}
// With $defs, nested types are extracted:
export interface Order {
orderId: string;
customer: Customer;
items: LineItem[];
total: Money;
status?: "pending" | "processing" | "shipped" | "delivered" | "cancelled";
}
export interface Customer { id: string; email: string; name?: string; }
export interface LineItem { productId: string; quantity: number; unitPrice: Money; }
export interface Money { amount: number; currency: string; } Our browser-only JSON Schema → TypeScript Generator handles this conversion entirely client-side — paste your JSON Schema and get TypeScript interfaces instantly, with support for $ref, $defs, composition keywords, and all Draft 7 and 2020-12 features.
Command-Line Generation (for CI/CD)
# Install json-schema-to-typescript:
npm install -g json-schema-to-typescript
# Generate TypeScript from a schema file:
json2ts --input user.schema.json --output user.ts
# Generate from multiple schemas (glob):
json2ts --input 'schemas/**/*.json' --output types/ AJV Type-Safe Validation
When using AJV with generated TypeScript types, you can get fully typed validation with a type guard:
import Ajv from "ajv/dist/2020";
import { User } from "./types/user";
import userSchema from "./schemas/user.schema.json";
const ajv = new Ajv({ strict: true });
const validate = ajv.compile<User>(userSchema);
function validateUser(data: unknown): User {
if (!validate(data)) throw new Error(ajv.errorsText(validate.errors));
return data; // TypeScript knows this is User
} 12. Using JSON Schema for API Validation
The most common production use of JSON Schema is validating API request bodies. Here's how to integrate schema validation at the framework level:
// Express.js with AJV middleware
import Ajv from "ajv/dist/2020";
import addFormats from "ajv-formats";
const ajv = new Ajv({ allErrors: true, strict: true });
addFormats(ajv); // add date-time, email, uuid format validation
function validateBody(schema: object) {
const validate = ajv.compile(schema);
return (req, res, next) => {
if (!validate(req.body)) {
return res.status(400).json({
error: "Validation failed",
details: ajv.errorsText(validate.errors, { separator: '; ' })
});
}
next();
};
}
// Use in route:
app.post('/users', validateBody(userSchema), createUserHandler); Use our JSON Schema Validator to quickly test if sample API payloads pass your schema before writing any code. If you have sample JSON data but no schema yet, use our JSON → JSON Schema Generator to auto-generate a starting schema from your example data.
Frequently Asked Questions
- What is JSON Schema?
- JSON Schema is a vocabulary for annotating and validating JSON documents. It is a JSON document that describes the expected structure, types, constraints, and allowed values of another JSON document. It is used for API validation, configuration file validation, TypeScript type generation, and OpenAPI documentation.
- What are the JSON Schema types?
- JSON Schema defines 7 primitive types: string, number, integer, boolean, null, object, and array. Each type has its own validation keywords — strings have minLength/maxLength/pattern, numbers have minimum/maximum/multipleOf, objects have properties/required/additionalProperties, and arrays have items/minItems/maxItems/uniqueItems.
- What is the difference between allOf, anyOf, and oneOf?
- allOf requires data to be valid against ALL listed schemas (TypeScript & intersection). anyOf requires valid against AT LEAST ONE schema (TypeScript | union). oneOf requires valid against EXACTLY ONE schema (exclusive union — mutually exclusive). Use anyOf for "this OR that" and oneOf for discriminated union patterns where exactly one branch applies.
- What is $ref in JSON Schema?
- $ref is a JSON Pointer reference to another schema, enabling reuse. Define reusable schemas in the $defs section (Draft 2020-12) or definitions (older drafts) and reference them with "$ref": "#/$defs/MyType". This is equivalent to TypeScript type aliases and prevents duplicating complex schemas across a large schema file.
- How do I generate TypeScript types from JSON Schema?
- Use the json-schema-to-typescript npm package for command-line generation. Our browser-based JSON Schema → TypeScript Generator does this client-side instantly — paste your schema and get TypeScript interfaces with full support for $ref, $defs, composition keywords, and discriminated unions.
- What is additionalProperties in JSON Schema?
- additionalProperties controls whether an object can have undeclared properties. Setting it to false makes the schema strict — any property not listed in properties causes validation failure (equivalent to TypeScript's strict object types). Setting it to a schema allows additional properties but requires them to match that schema.