Skip to main content
DevBench
All articles
jsonvalidationjavascript

JSON Schema Validation: A Practical Guide with Examples

June 27, 20267 min read

JSON is flexible by design — any key, any value, any depth. That flexibility is great for prototyping and terrible for production APIs. JSON Schema is the standard way to describe what a valid JSON document must look like: which fields are required, what types they must be, and what constraints they must satisfy.

What JSON Schema actually is

A JSON Schema is itself a JSON document. You write it once, then use it to validate any number of input documents against it. The schema lives at $schema: "https://json-schema.org/draft/2020-12/schema" by convention. Libraries like AJV (Node.js), jsonschema (Python), and Newtonsoft.Json.Schema (.NET) implement validation against it.

A minimal schema looks like this:

{
  "$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 },
    "email": { "type": "string",  "format": "email" }
  },
  "additionalProperties": false
}

This schema says: the document must be an object with three required fields. id must be a positive integer, name a non-empty string, email a valid email address, and no extra fields are allowed.

The six core keywords

KeywordWhat it doesExample
typeConstrains the JSON type"type": "string"
requiredLists mandatory keys in an object"required": ["id"]
propertiesDefines sub-schemas for each key"properties": { "age": { ... } }
enumRestricts value to a fixed set"enum": ["active", "inactive"]
minimum / maximumNumeric range constraints"minimum": 0, "maximum": 100
patternRegex the string must match"pattern": "^[A-Z]{2}\d{4}$"

String constraints

  • minLength / maxLength — character count bounds
  • pattern — a regex the value must satisfy (anchored to the full string)
  • format — semantic hints: email, uri, date, date-time, uuid. Note: formats are annotations by default — validators only enforce them if you opt in (e.g. ajv.opts.formats)

Array constraints

  • items — the schema every element must satisfy
  • minItems / maxItems — length bounds
  • uniqueItems: true — no duplicates allowed
{
  "type": "array",
  "items": { "type": "string" },
  "minItems": 1,
  "uniqueItems": true
}

Composition keywords

JSON Schema has four composition keywords that let you build complex rules from simpler schemas:

  • allOf — must satisfy every listed schema (AND)
  • anyOf — must satisfy at least one listed schema (OR)
  • oneOf — must satisfy exactly one listed schema (XOR)
  • not — must not satisfy the given schema

A common pattern is anyOf for nullable fields: "anyOf": [{ "type": "string" }, { "type": "null" }]. In JSON Schema 2019-09+ you can write this more concisely as "type": ["string", "null"].

Validating with AJV in Node.js

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

const ajv = new Ajv();
addFormats(ajv); // enables "email", "uri", "date-time" etc.

const schema = {
  type: "object",
  required: ["id", "name"],
  properties: {
    id:   { type: "integer", minimum: 1 },
    name: { type: "string", minLength: 1 },
  },
  additionalProperties: false,
};

const validate = ajv.compile(schema);
const valid = validate({ id: 1, name: "Alice" });

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

ajv.compile() returns a reusable validation function — compile once, call many times. The validate.errors array contains detailed error objects with the failing path, keyword, and message.

$ref and reusable definitions

Large schemas use $defs (formerly definitions) and $ref to avoid repetition:

{
  "$defs": {
    "Address": {
      "type": "object",
      "required": ["street", "city"],
      "properties": {
        "street": { "type": "string" },
        "city":   { "type": "string" }
      }
    }
  },
  "type": "object",
  "properties": {
    "billing":  { "$ref": "#/$defs/Address" },
    "shipping": { "$ref": "#/$defs/Address" }
  }
}

Common mistakes

  • Forgetting additionalProperties: false — without it, extra fields silently pass validation
  • Confusing format enforcement"format": "email" is not enforced unless your validator is configured to check formats
  • Using oneOf where anyOf is correctoneOf fails if more than one sub-schema matches, which is rarely what you want
  • Schema draft mismatch — AJV v8 defaults to Draft 2020-12; older tooling may use Draft 4 or 7 with different keyword semantics

Try it yourself

Use the free browser-based JSON Formatter & Validator on DevBench — no signup, runs entirely in your browser.

Open JSON Formatter & Validator