NexusCS

JSON Schema

JSON
Quick reference for JSON Schema Draft 2020-12 - validate JSON documents with declarative schemas
validation
json
schema
api

Getting started

Introduction

JSON Schema is a vocabulary for annotating and validating JSON documents. It's platform-independent and widely used for API validation.

Current Specification: Draft 2020-12
Meta-schema: https://json-schema.org/draft/2020-12/schema

Minimal Schema

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/product.schema.json",
  "title": "Product",
  "description": "A product in the catalog",
  "type": "object"
}

The $schema keyword declares which JSON Schema version is used.

Basic Types

{
  "type": "string"
}

Seven basic types:

Type Description
string Text values
number Numeric values (float)
integer Whole numbers
boolean true or false
array Ordered lists
object Key-value pairs
null Null value

Multiple Types

{
  "type": ["string", "number"]
}

Accepts either a string or a number.

Core Keywords

Schema Metadata

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/user.json",
  "title": "User",
  "description": "A registered user",
  "default": {},
  "examples": [
    {
      "name": "Alice",
      "email": "alice@example.com"
    }
  ]
}
Keyword Purpose
$schema Declares schema version
$id Unique identifier (URI)
title Human-readable title
description Detailed explanation
default Default value
examples Example valid data

References

{
  "$defs": {
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" }
      }
    }
  },
  "type": "object",
  "properties": {
    "billing": { "$ref": "#/$defs/address" },
    "shipping": { "$ref": "#/$defs/address" }
  }
}

Use $ref to reuse schema definitions.

External References

{
  "type": "object",
  "properties": {
    "user": {
      "$ref": "https://example.com/schemas/user.json"
    }
  }
}

Reference schemas from external files or URLs.

String Validation

Length Constraints

{
  "type": "string",
  "minLength": 3,
  "maxLength": 20
}

Restricts string length (inclusive).

Pattern Matching

{
  "type": "string",
  "pattern": "^[A-Za-z0-9_]+$"
}

Validates against a regular expression (ECMA-262).

Format Validation

{
  "type": "string",
  "format": "email"
}

Built-in format validators:

Date/Time Formats

Format Example
date-time 2024-12-31T23:59:59Z
date 2024-12-31
time 23:59:59
duration P3Y6M4DT12H30M5S

Network Formats

Format Example
email user@example.com
idn-email 用户@例え.jp
hostname example.com
idn-hostname 例え.jp
ipv4 192.168.1.1
ipv6 ::1

URI Formats

Format Description
uri Full URI
uri-reference URI or relative ref
iri Internationalized URI
iri-reference IRI or relative ref
uri-template RFC 6570 template

Other Formats

Format Description
uuid RFC 4122 UUID
json-pointer RFC 6901 pointer
relative-json-pointer Relative pointer
regex Regular expression

Number Validation

Range Constraints

{
  "type": "number",
  "minimum": 0,
  "maximum": 100
}

Inclusive minimum and maximum values.

Exclusive Range

{
  "type": "number",
  "exclusiveMinimum": 0,
  "exclusiveMaximum": 100
}

Exclusive boundaries (0 < x < 100).

Multiple Of

{
  "type": "number",
  "multipleOf": 0.01
}

Value must be a multiple of the given number (useful for currency: 2 decimal places).

Integer Type

{
  "type": "integer",
  "minimum": 1,
  "maximum": 10
}

Integers are a subset of numbers (no decimals).

Array Validation

Items Schema

{
  "type": "array",
  "items": {
    "type": "string"
  }
}

All items must match the schema (homogeneous array).

Tuple Validation

{
  "type": "array",
  "prefixItems": [
    { "type": "number" },
    { "type": "string" },
    { "type": "boolean" }
  ]
}

Validates positional items (heterogeneous array/tuple).

Array Length

{
  "type": "array",
  "minItems": 1,
  "maxItems": 10
}

Constrains array size.

Unique Items

{
  "type": "array",
  "uniqueItems": true
}

All items must be unique (set semantics).

Contains

{
  "type": "array",
  "contains": {
    "type": "string",
    "pattern": "^admin"
  },
  "minContains": 1,
  "maxContains": 3
}

Array must contain at least minContains items matching the schema.

Object Validation

Properties

{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer" },
    "email": {
      "type": "string",
      "format": "email"
    }
  }
}

Defines expected properties and their schemas.

Required Properties

{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "email": { "type": "string" }
  },
  "required": ["name", "email"]
}

Lists mandatory properties.

Additional Properties

{
  "type": "object",
  "properties": {
    "name": { "type": "string" }
  },
  "additionalProperties": false
}

Set to false to disallow unlisted properties.

{
  "type": "object",
  "properties": {
    "name": { "type": "string" }
  },
  "additionalProperties": {
    "type": "string"
  }
}

Or provide a schema for additional properties.

Pattern Properties

{
  "type": "object",
  "patternProperties": {
    "^[0-9]+$": {
      "type": "number"
    },
    "^[a-z]+$": {
      "type": "string"
    }
  }
}

Schema for properties matching regex patterns.

Property Names

{
  "type": "object",
  "propertyNames": {
    "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
  }
}

Validates all property names against a schema.

Size Constraints

{
  "type": "object",
  "minProperties": 1,
  "maxProperties": 10
}

Limits number of properties in the object.

Combining Schemas

allOf (AND)

{
  "allOf": [
    { "type": "object" },
    {
      "properties": {
        "name": { "type": "string" }
      }
    },
    {
      "properties": {
        "age": { "type": "integer" }
      }
    }
  ]
}

Data must be valid against all subschemas.

anyOf (OR)

{
  "anyOf": [{ "type": "string" }, { "type": "number" }]
}

Data must be valid against at least one subschema.

oneOf (XOR)

{
  "oneOf": [
    {
      "properties": {
        "type": { "const": "credit_card" },
        "card_number": { "type": "string" }
      }
    },
    {
      "properties": {
        "type": { "const": "bank_account" },
        "account_number": { "type": "string" }
      }
    }
  ]
}

Data must be valid against exactly one subschema.

not (NOT)

{
  "not": {
    "type": "null"
  }
}

Data must not be valid against the subschema.

Conditional Validation

if/then/else

{
  "type": "object",
  "properties": {
    "country": { "type": "string" },
    "postal_code": { "type": "string" }
  },
  "if": {
    "properties": {
      "country": { "const": "USA" }
    }
  },
  "then": {
    "properties": {
      "postal_code": {
        "pattern": "^[0-9]{5}(-[0-9]{4})?$"
      }
    }
  },
  "else": {
    "properties": {
      "postal_code": {
        "pattern": "^[A-Z0-9]+$"
      }
    }
  }
}

Apply different schemas based on conditions.

Dependent Schemas

{
  "type": "object",
  "properties": {
    "credit_card": { "type": "string" }
  },
  "dependentSchemas": {
    "credit_card": {
      "properties": {
        "billing_address": { "type": "string" }
      },
      "required": ["billing_address"]
    }
  }
}

When credit_card is present, billing_address becomes required.

Dependent Required

{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "credit_card": { "type": "string" },
    "billing_address": { "type": "string" }
  },
  "dependentRequired": {
    "credit_card": ["billing_address"]
  }
}

Simpler syntax when only requiring fields conditionally.

Generic Keywords

Enumeration

{
  "type": "string",
  "enum": ["red", "green", "blue"]
}

Value must be one of the enumerated values.

Constant

{
  "type": "string",
  "const": "active"
}

Value must exactly match the constant.

Annotations

{
  "type": "string",
  "deprecated": true,
  "readOnly": true,
  "writeOnly": false
}
Keyword Purpose
deprecated Mark as deprecated
readOnly For API responses only
writeOnly For API requests only

Complete Examples

User Registration Schema

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/user-registration.schema.json",
  "title": "User Registration",
  "description": "Schema for user registration form",
  "type": "object",
  "properties": {
    "username": {
      "type": "string",
      "minLength": 3,
      "maxLength": 20,
      "pattern": "^[a-zA-Z0-9_]+$",
      "description": "Unique username (alphanumeric and underscore)"
    },
    "email": {
      "type": "string",
      "format": "email",
      "description": "Valid email address"
    },
    "password": {
      "type": "string",
      "minLength": 8,
      "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$",
      "description": "Password with uppercase, lowercase, digit, and special char"
    },
    "age": {
      "type": "integer",
      "minimum": 18,
      "maximum": 120,
      "description": "User must be 18 or older"
    },
    "terms_accepted": {
      "type": "boolean",
      "const": true,
      "description": "User must accept terms"
    }
  },
  "required": ["username", "email", "password", "terms_accepted"]
}

Product Catalog Schema

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/product.schema.json",
  "title": "Product",
  "description": "A product in the catalog",
  "type": "object",
  "$defs": {
    "price": {
      "type": "number",
      "minimum": 0,
      "multipleOf": 0.01
    },
    "dimension": {
      "type": "object",
      "properties": {
        "length": { "type": "number", "minimum": 0 },
        "width": { "type": "number", "minimum": 0 },
        "height": { "type": "number", "minimum": 0 }
      },
      "required": ["length", "width", "height"]
    }
  },
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "Unique product identifier"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "price": {
      "$ref": "#/$defs/price"
    },
    "discount_price": {
      "$ref": "#/$defs/price"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "uniqueItems": true,
      "minItems": 1
    },
    "dimensions": {
      "$ref": "#/$defs/dimension"
    },
    "in_stock": {
      "type": "boolean"
    },
    "category": {
      "type": "string",
      "enum": ["electronics", "clothing", "food", "books", "other"]
    }
  },
  "required": ["id", "name", "price", "in_stock", "category"],
  "if": {
    "properties": {
      "discount_price": { "type": "number" }
    },
    "required": ["discount_price"]
  },
  "then": {
    "properties": {
      "discount_price": {
        "type": "number",
        "exclusiveMaximum": { "$data": "1/price" }
      }
    }
  }
}

API Response with Polymorphism

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/api-response.schema.json",
  "title": "API Response",
  "type": "object",
  "properties": {
    "status": {
      "type": "string",
      "enum": ["success", "error"]
    }
  },
  "required": ["status"],
  "oneOf": [
    {
      "properties": {
        "status": { "const": "success" },
        "data": {
          "type": "object",
          "description": "Response payload"
        }
      },
      "required": ["data"]
    },
    {
      "properties": {
        "status": { "const": "error" },
        "error": {
          "type": "object",
          "properties": {
            "code": { "type": "integer" },
            "message": { "type": "string" }
          },
          "required": ["code", "message"]
        }
      },
      "required": ["error"]
    }
  ]
}

Validators & Tools

JavaScript

npm install ajv
import Ajv from "ajv";
import addFormats from "ajv-formats";

const ajv = new Ajv();
addFormats(ajv);

const schema = {
  type: "object",
  properties: {
    email: { type: "string", format: "email" },
  },
  required: ["email"],
};

const validate = ajv.compile(schema);
const valid = validate({ email: "user@example.com" });

if (!valid) {
  console.log(validate.errors);
}

Popular libraries:

  • ajv - Fast and feature-complete
  • @hyperjump/json-schema - Spec-compliant
  • jsonschema - Simple validator

Python

pip install jsonschema
from jsonschema import validate, ValidationError

schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer", "minimum": 0}
    },
    "required": ["name"]
}

data = {"name": "Alice", "age": 30}

try:
    validate(instance=data, schema=schema)
    print("Valid!")
except ValidationError as e:
    print(f"Invalid: {e.message}")

Popular libraries:

  • jsonschema - Reference implementation
  • fastjsonschema - High performance

Other Languages

Language Library
Java json-schema-validator (networknt)
Go gojsonschema
.NET/C# Newtonsoft.Json.Schema
Ruby json-schema gem
PHP justinrainbow/json-schema
Rust jsonschema crate

Gotchas

Schema vs. Meta-schema

Schema validates your data.
Meta-schema validates your schema.

Always include $schema to declare which JSON Schema version you're using.

additionalProperties Default

By default, additionalProperties is true (allows any extra properties).

{
  "type": "object",
  "properties": {
    "name": { "type": "string" }
  }
}

This allows { "name": "Alice", "age": 30 }.

To disallow: set "additionalProperties": false.

Pattern is Anchored

Patterns are implicitly anchored (match entire string).

{ "pattern": "abc" }

Matches "abc" anywhere in the string. Use ^abc$ for exact match.

Format is Optional

The format keyword is for validation but may be ignored by validators.

Always check your validator's format support. Use ajv-formats with AJV.

$ref Replaces Sibling Keywords

In older drafts, sibling keywords to $ref were ignored.

{
  "$ref": "#/$defs/address",
  "description": "This might be ignored!"
}

Draft 2019-09+: Sibling keywords are now respected.

Integer vs. Number

integer is a distinct type from number.

{ "type": "integer" }

Rejects 1.0 even though mathematically equal to 1 in some validators.

Empty Arrays and Objects

Empty arrays/objects are valid by default.

Use minItems: 1 or minProperties: 1 to require content.

Also see