DEV Community

Snappy Tools
Snappy Tools

Posted on

JSON Schema: Validate Your API Data Before It Reaches Your Application

Every developer has encountered the runtime error that happens when an API returns something unexpected: a field that should be a number is a string, a required field is missing, an array contains a null. JSON Schema is the standard way to define what your JSON data should look like — and validate it before your application processes it.

What is JSON Schema?

JSON Schema is itself a JSON document that describes the expected structure of another JSON document. It specifies:

  • Which fields are required vs optional
  • What type each field should be
  • Constraints on values (min/max, patterns, enums)
  • Nested object and array shapes
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["id", "name", "email"],
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "role": {
      "type": "string",
      "enum": ["admin", "user", "viewer"]
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "uniqueItems": true
    },
    "createdAt": {
      "type": "integer",
      "description": "Unix timestamp"
    }
  },
  "additionalProperties": false
}
Enter fullscreen mode Exit fullscreen mode

Core types

JSON Schema supports these primitive types:

  • "string" — any string value
  • "number" — any number (integer or float)
  • "integer" — integer only
  • "boolean"true or false
  • "null"null only
  • "array" — JSON array
  • "object" — JSON object
  • "type": ["string", "null"] — multiple types (nullable)

String constraints

{
  "type": "string",
  "minLength": 1,
  "maxLength": 255,
  "pattern": "^[a-z0-9-]+$",  // regex
  "format": "email"  // built-in format validators
}
Enter fullscreen mode Exit fullscreen mode

Built-in formats: "email", "uri", "date", "time", "date-time", "ipv4", "ipv6", "uuid". Note: format validation is optional in the spec — check your library's documentation.

Number constraints

{
  "type": "number",
  "minimum": 0,
  "maximum": 100,
  "exclusiveMinimum": true,  // 0 is not valid, only > 0
  "multipleOf": 0.5  // must be a multiple of 0.5
}
Enter fullscreen mode Exit fullscreen mode

Object constraints

{
  "type": "object",
  "required": ["name", "email"],
  "properties": {
    "name": { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "nickname": { "type": "string" }
  },
  "additionalProperties": false,  // reject unknown keys
  "minProperties": 2,
  "maxProperties": 10
}
Enter fullscreen mode Exit fullscreen mode

additionalProperties: false is strict — only keys listed in properties are allowed. Useful for catching typos in field names during development.

Array constraints

{
  "type": "array",
  "items": { "type": "string" },  // all items must be strings
  "minItems": 1,
  "maxItems": 100,
  "uniqueItems": true
}
Enter fullscreen mode Exit fullscreen mode

For heterogeneous arrays where each position has a known type (a "tuple"):

{
  "type": "array",
  "prefixItems": [
    { "type": "string" },    // position 0: string
    { "type": "number" },    // position 1: number
    { "type": "boolean" }    // position 2: boolean
  ],
  "items": false  // no additional items allowed
}
Enter fullscreen mode Exit fullscreen mode

Conditional schemas

{
  "type": "object",
  "properties": {
    "type": { "type": "string", "enum": ["user", "admin"] },
    "adminLevel": { "type": "integer" }
  },
  "if": {
    "properties": { "type": { "const": "admin" } }
  },
  "then": {
    "required": ["adminLevel"]
  }
}
Enter fullscreen mode Exit fullscreen mode

adminLevel is only required when type is "admin".

Composition with allOf, anyOf, oneOf

{
  "allOf": [
    { "$ref": "#/$defs/BaseUser" },
    { "required": ["adminLevel"] }
  ]
}
Enter fullscreen mode Exit fullscreen mode
{
  "anyOf": [
    { "type": "string" },
    { "type": "null" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

allOf — must match all schemas. anyOf — must match at least one. oneOf — must match exactly one.

Reusable definitions

{
  "$defs": {
    "Address": {
      "type": "object",
      "required": ["street", "city"],
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "country": { "type": "string", "default": "US" }
      }
    }
  },
  "type": "object",
  "properties": {
    "billingAddress": { "$ref": "#/$defs/Address" },
    "shippingAddress": { "$ref": "#/$defs/Address" }
  }
}
Enter fullscreen mode Exit fullscreen mode

$ref references any schema by its JSON Pointer path.

Validation in JavaScript (Ajv)

Ajv is the most widely used JSON Schema validator for JavaScript:

import Ajv from 'ajv';
import addFormats from 'ajv-formats';

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

const schema = {
  type: 'object',
  required: ['name', 'email'],
  properties: {
    name: { type: 'string', minLength: 1 },
    email: { type: 'string', format: 'email' },
  },
};

const validate = ajv.compile(schema);

const data = { name: 'Alice', email: 'alice@example.com' };

if (!validate(data)) {
  console.error(validate.errors);
  // [{ instancePath: '/email', message: 'must match format "email"' }]
} else {
  console.log('Valid!');
}
Enter fullscreen mode Exit fullscreen mode

Validation in Python (jsonschema)

from jsonschema import validate, ValidationError

schema = {
    "type": "object",
    "required": ["name", "email"],
    "properties": {
        "name": {"type": "string", "minLength": 1},
        "email": {"type": "string", "format": "email"},
    }
}

data = {"name": "Alice", "email": "not-an-email"}

try:
    validate(instance=data, schema=schema)
except ValidationError as e:
    print(e.message)  # 'not-an-email' is not a 'email'
Enter fullscreen mode Exit fullscreen mode

Where to use schema validation

Incoming API requests — validate request bodies before passing to business logic. Return 400 with a structured error message if validation fails.

Outgoing API responses — validate your own API output in tests to catch field-dropping regressions.

Configuration files — validate JSON/YAML config at startup before the application runs.

Message queue payloads — validate events before processing to avoid corrupt state.

Formatting and inspecting JSON

When debugging schemas or API responses, a formatter helps spot structural issues at a glance. The JSON Formatter formats, validates, and syntax-highlights JSON in the browser with no upload required — useful for inspecting API responses from your browser's Network tab.


JSON Schema is one of those tools that pays back every hour you put into it. A schema you write once catches an entire class of bugs across development, testing, and production — before they hit your error handling or, worse, your database.

Top comments (0)