October 4, 2026 · Yunus Emre Vurgun

How Do I Validate JSON with JSON Schema?

json · data · reference · tutorial

JSON Schema validates JSON by describing the shape your data must have — its types, required fields, and allowed values — in a JSON document of its own. You write the schema once, then run any payload through a validator library in your language to get a pass or fail verdict plus a list of violations. This primer covers the keywords you will use in ninety percent of schemas, with examples you can adapt directly.

What a schema actually declares

A schema is a contract: it lists what is allowed and, by default, permits everything it does not forbid. The smallest useful schema declares a type:

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

This schema accepts {"name": "Ada"} and {"name": "Ada", "age": 36}, but rejects {"age": -1} twice over: the required name is missing and age violates its minimum. Note that an object with an extra undeclared property still passes, because extra properties are allowed unless you set "additionalProperties": false. That permissive default surprises newcomers, but it is deliberate: it lets schemas evolve without breaking older writers. Tighten it only where unknown fields are genuinely dangerous, such as security-sensitive configuration.

Types, required fields, and enums

The type keyword accepts string, number, integer, boolean, array, object, and null. Each type has its own constraint keywords, and the most used ones fit in one table:

KeywordApplies toWhat it does
requiredobjectsLists property names that must be present.
enumanyRestricts a value to an exact list, such as draft or published.
minimum / maximumnumbersInclusive numeric bounds; add exclusiveMinimum for strict ones.
minLength / maxLengthstringsLength bounds in characters; pattern adds a regex constraint.
formatstringsAnnotation such as email or date-time; often not enforced unless enabled.
minItems / maxItemsarraysLength bounds; uniqueItems: true forbids duplicates.
additionalPropertiesobjectsSet to false or a schema to control undeclared properties.

Two warnings pay for themselves. First, format is usually annotation-only: many validators accept a bogus address for an email format unless you explicitly enable format assertion, so do not rely on it for security checks. Second, JSON has no integer-versus-float distinction at the syntax level, so 1.0 may validate as integer in some implementations and fail in others — if the distinction matters, check it in application code too. For the encoding details that affect string validation, see units, dates, and encodings.

Nested objects and arrays

Real payloads nest, and schemas nest with them. Use properties for objects and items for arrays, composing them to any depth:

{
  "type": "object",
  "properties": {
    "tags": {
      "type": "array",
      "items": { "type": "string", "minLength": 1 },
      "maxItems": 8
    },
    "author": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "email": { "type": "string" }
      },
      "required": ["name"],
      "additionalProperties": false
    }
  },
  "required": ["tags"]
}

For schemas that repeat a definition, $defs with $ref keeps things DRY: define the address shape once under $defs/address and reference it from both billing and shipping with a local ref. Composition keywords add logic: allOf requires every subschema to pass, anyOf requires at least one, and oneOf requires exactly one. Prefer anyOf over oneOf unless exclusivity is the point — overlapping subschemas make oneOf fail in confusing ways.

How do I validate JSON in code?

Every mainstream language has a solid validator, and they all follow the same three steps: parse the schema, parse the instance, collect errors. In Python with the jsonschema package:

import jsonschema
validator = jsonschema.Draft202012Validator(schema)
errors = sorted(validator.iter_errors(payload), key=lambda e: e.path)
for e in errors:
    print(list(e.path), e.message)

The same pattern holds for Ajv in JavaScript, everit or networknt in Java, and santhosh-tekuri in Go: compile once at startup, validate per request, and return the error list to the caller. Two practices matter in production. First, validate at the boundary — the moment data enters your system — so everything downstream can trust the shape. Second, cap payload size before validating, since deeply nested input can make validation expensive; a small body-size limit plus a parse timeout defeats that nicely. Schemas also pair well with compact formats: if you shrink payloads before sending them, compare CSV vs JSON vs TOON and mind the cost of pretty-printing JSON.

Mistakes that silently pass

The most expensive schema bugs are the ones that accept bad data quietly. Forgetting required is the classic: a schema with only properties happily accepts an empty object, because every property is optional by default. Leaving additionalProperties open when you meant a closed shape lets typos like emial sail through next to the real email. A wrong type at the top, such as declaring object when the endpoint can also return an array, rejects valid responses and pages someone at night.

A fourth trap is version drift between the schema and the code it guards: the API gains a new optional field, the schema is never updated, and strict consumers with closed shapes start rejecting valid payloads. Keep the schema in the same repository as the code it describes, review schema changes alongside the code changes they accompany, and publish versioned copies so consumers can pin what they validate against.

Guard against all three with negative tests: for every schema, keep a small file of payloads that must fail and assert that they do. Test the empty object, the typo field, the wrong top-level type, and the boundary values just inside and outside each numeric limit. And when your API consumes reference data from elsewhere, check how the provider versions its shapes — the API documentation shows how this site keeps dataset formats stable for consumers.