Micro Tool Yard logo
Tools

Guide

JSON Schema Basics

An introduction to describing the shape of your JSON data with JSON Schema, and how to use it to catch bad data before it causes a bug somewhere else.

JSON itself has no way to describe what a valid document is supposed to look like. A JSON parser will happily accept {"age": "twelve"} even if your application needs age to be a number — the parser only checks that the syntax is valid JSON, not that the data makes sense. JSON Schema fills that gap: it's a JSON document that describes the expected shape of another JSON document, so you can validate data against a contract before your code ever tries to use it.

Why this matters in practice

Without a schema, a malformed or unexpected API response tends to fail late and confusingly — deep inside your application logic, often as a cryptic type error or a silent bug where a missing field just becomes undefined and propagates. Validating against a schema at the boundary (right when data enters your system — an API response, a config file, a form submission) turns that into a clear, immediate error: "field age must be a number, got a string," reported at the moment the bad data arrives, not three function calls later.

The basic building blocks

A JSON Schema document is itself JSON, and describes constraints using keywords. The most fundamental is type, which restricts a value to one of JSON's basic types:string, number, integer, boolean,object, array, or null.

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

This schema says: the data must be an object; if it has a name property, that property must be a string; if it has an age property, it must be a whole number of at least zero; and name specifically must be present (while ageis optional, since it isn't listed in required).

Constraining values, not just types

Beyond basic types, JSON Schema lets you constrain the actual values: minimum andmaximum for numbers, minLength and maxLength for strings, pattern for a regular expression a string must match, andenum for "must be exactly one of these specific values." A field likestatus that should only ever be "active", "paused", or"cancelled" is a natural fit for enum — it catches a typo like"actve" immediately, rather than letting it silently become a fourth, unintended status value somewhere downstream.

Arrays and nested objects

Real-world data is rarely flat, and schemas compose naturally to describe nested structure. An array of objects uses items to describe what each element must look like:

{
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "id": { "type": "string" },
      "quantity": { "type": "integer", "minimum": 1 }
    },
    "required": ["id", "quantity"]
  }
}

This describes a list where every element must be an object with a string id and an integer quantity of at least 1 — exactly the shape you'd want to validate a shopping cart or an order line-item list against, before trusting it enough to compute a total.

Additional properties: open or closed?

By default, JSON Schema's properties keyword doesn't forbid extra fields — a document with properties beyond the ones listed is still considered valid unless you say otherwise. Setting "additionalProperties": false makes the schema strict: any property not explicitly listed causes validation to fail. This is a genuinely important design decision, not just a stylistic one. A permissive schema (the default) is more forgiving of API evolution — a service can add a new field without breaking existing validators — but won't catch a typo'd field name ("nmae" instead of "name") since it just looks like an allowed extra property. A strict schema catches typos immediately but requires updating the schema every time a new legitimate field is added. Which one is right depends on whether you're validating data you control (where strictness helps catch mistakes early) or data from a third party you don't control (where some permissiveness avoids breaking on additions you didn't ask for).

A practical workflow

A common, effective pattern: write a schema for the data your application actually depends on — not necessarily every field a document might contain, just the fields you read and the constraints you're relying on being true. Validate incoming data against that schema at the boundary (when you receive an API response, load a config file, or accept a form submission). When validation fails, surface the specific error — which field, what was expected, what was found — rather than a generic "invalid data" message. This turns a class of bugs that would otherwise surface confusingly deep in your application into a clear, immediate, and actionable error message at the one place where it's cheapest to fix: right where the bad data entered the system.

Schemas as documentation, too

There's a secondary benefit worth calling out: a schema doubles as precise, unambiguous documentation of a data shape, in a way that a prose description or a single example payload never quite manages. An example shows you one valid document; it can't tell you whether a field is required, whether an array can be empty, or whether a number needs to be a positive integer specifically or just any number. A schema answers all of that directly, and because it's machine-readable, it can be checked automatically rather than trusted to stay accurate as the underlying data format evolves — a stale comment can drift from reality silently, but a schema that no longer matches the data it's meant to describe will start failing validation, which is a much louder and more useful signal that something needs to be reconciled.