DevBolt
·9 min read

JSON Schema: A Practical Guide to Validating JSON Data

JSONValidationAPI

JSON Schema is a vocabulary for describing and validating the structure of JSON data. If you've ever wished you could enforce types on a JSON API response the way TypeScript enforces types in code, JSON Schema is the answer. This guide covers the core concepts you need to start validating JSON today.

What Is JSON Schema?

A JSON Schema is itself a JSON document that describes what valid data looks like. It defines types, required fields, value constraints, patterns, and relationships. Validators use the schema to check whether a given JSON document conforms.

Common use cases include:

  • Validating API request and response bodies
  • Documenting data formats (OpenAPI uses JSON Schema)
  • Generating forms and UI from data models
  • Configuration file validation (e.g., VS Code settings, ESLint config)

Your First Schema

Here's a simple schema for a user object:

JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "age": { "type": "integer", "minimum": 0 }
  },
  "required": ["name", "email"]
}

This schema says: the data must be an object with name (string, required), email (string in email format, required), and age (non-negative integer, optional).

Types

JSON Schema supports seven primitive types:

TypeDescription
stringText values
numberAny numeric value (integer or float)
integerWhole numbers only
booleantrue or false
objectKey-value pairs
arrayOrdered list of values
nullThe null value

String Validation

Strings support length constraints, patterns, and built-in formats:

JSON Schema
{
  "type": "string",
  "minLength": 1,
  "maxLength": 255,
  "pattern": "^[a-zA-Z0-9_]+$"
}

// Built-in formats (not all validators enforce these):
{ "type": "string", "format": "email" }
{ "type": "string", "format": "uri" }
{ "type": "string", "format": "date-time" }
{ "type": "string", "format": "uuid" }
{ "type": "string", "format": "ipv4" }
{ "type": "string", "format": "ipv6" }

Number Validation

Numbers can be constrained to ranges and multiples:

JSON Schema
{
  "type": "number",
  "minimum": 0,
  "maximum": 100,
  "exclusiveMinimum": 0,
  "multipleOf": 0.01
}

// Integer with enum (only these values are valid)
{
  "type": "integer",
  "enum": [1, 2, 3, 5, 8, 13]
}

Objects and Required Fields

Objects are defined by their properties. Use required to list fields that must be present, and additionalProperties to control whether extra fields are allowed:

JSON Schema
{
  "type": "object",
  "properties": {
    "id": { "type": "integer" },
    "name": { "type": "string" },
    "role": {
      "type": "string",
      "enum": ["admin", "user", "viewer"]
    }
  },
  "required": ["id", "name"],
  "additionalProperties": false
}

Setting additionalProperties: false makes the schema strict — any key not listed in properties will fail validation. This catches typos and prevents unexpected data from slipping through.

Arrays

Arrays are validated by the schema of their items, plus optional length and uniqueness constraints:

JSON Schema
{
  "type": "array",
  "items": { "type": "string" },
  "minItems": 1,
  "maxItems": 10,
  "uniqueItems": true
}

// Array of objects
{
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "id": { "type": "integer" },
      "tag": { "type": "string" }
    },
    "required": ["id", "tag"]
  }
}

Reuse with $ref and $defs

Avoid repeating yourself by defining reusable schemas with $defs and referencing them with $ref:

JSON Schema
{
  "$defs": {
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "zip": { "type": "string", "pattern": "^\\d{5}$" }
      },
      "required": ["street", "city", "zip"]
    }
  },
  "type": "object",
  "properties": {
    "billing": { "$ref": "#/$defs/address" },
    "shipping": { "$ref": "#/$defs/address" }
  }
}

The $ref pointer #/$defs/address means “look in the current document, under $defs, at the address key.” You can also reference schemas in external files.

Composition: oneOf, anyOf, allOf

Combine schemas with logical operators for more complex validation:

JSON Schema
// Value must match exactly one schema
{
  "oneOf": [
    { "type": "string" },
    { "type": "integer" }
  ]
}

// Value must match at least one schema
{
  "anyOf": [
    { "type": "string", "minLength": 1 },
    { "type": "null" }
  ]
}

// Value must match ALL schemas (intersection)
{
  "allOf": [
    { "$ref": "#/$defs/baseUser" },
    {
      "properties": {
        "role": { "const": "admin" }
      }
    }
  ]
}

Conditional Validation

Use if/then/else for fields that depend on other fields:

JSON Schema
{
  "type": "object",
  "properties": {
    "type": { "enum": ["email", "sms"] },
    "value": { "type": "string" }
  },
  "if": {
    "properties": { "type": { "const": "email" } }
  },
  "then": {
    "properties": { "value": { "format": "email" } }
  },
  "else": {
    "properties": { "value": { "pattern": "^\\+?[0-9]{10,15}$" } }
  }
}

If type is "email", the value must be a valid email. Otherwise, it must look like a phone number.

Real-World Example: API Response

Here's a complete schema for a paginated API response:

JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string", "minLength": 1 },
          "email": { "type": "string", "format": "email" },
          "createdAt": { "type": "string", "format": "date-time" }
        },
        "required": ["id", "name", "email", "createdAt"],
        "additionalProperties": false
      }
    },
    "meta": {
      "type": "object",
      "properties": {
        "page": { "type": "integer", "minimum": 1 },
        "perPage": { "type": "integer", "minimum": 1, "maximum": 100 },
        "total": { "type": "integer", "minimum": 0 }
      },
      "required": ["page", "perPage", "total"]
    }
  },
  "required": ["data", "meta"]
}

Validating API payloads in production?

DigitalOcean App Platform deploys your Node.js, Python, or Go API from a Git repo with auto-scaling, free SSL, and built-in monitoring. Add JSON Schema validation to your endpoints and ship with confidence.

Validate Your Schemas

Use our JSON Schema Validator to test your schemas against sample data instantly. For generating TypeScript-style runtime validators, try the Zod Schema Generator. Both tools run entirely in your browser with no signup required.