JSON Formatter Hub

JSON Schema: How to Validate Your JSON Data Structure

JSON is flexible by design — a value can be a string, a number, an array, or null, and nothing in the format itself prevents you from sending the wrong shape to an API. JSON Schema fills that gap. It is a vocabulary for describing the structure of a JSON document, and it lets you enforce types, required fields, string patterns, numeric ranges, and much more — all without writing a single line of application code to do the checking yourself.

JSON Schema is defined by an IETF draft standard (currently draft 2020-12) and is entirely separate from JSON itself. A schema is just a JSON document that describes what another JSON document should look like. That separation means any language with a JSON parser can also support JSON Schema validation, and implementations exist for JavaScript, Python, Java, Ruby, Go, Rust, and many others.

The $schema and $id Keywords

Every schema should start with two meta-keywords. The $schema keyword declares which version of the JSON Schema specification the document follows. This matters because keyword behaviour changed between drafts. The $id keyword gives the schema a canonical URI — useful when schemas reference each other or are published for others to consume.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/user-registration.json",
  "title": "User Registration",
  "description": "Schema for a new user sign-up payload",
  "type": "object"
}

Neither keyword is required for a schema to work locally, but including them is considered best practice and makes schemas self-documenting.

Core Keywords: type, properties, required, additionalProperties

The type keyword is the most fundamental constraint. Valid values are "string", "number", "integer", "boolean", "array", "object", and "null". You can also pass an array of types when a field may legitimately hold more than one type, such as ["string", "null"] for a nullable field.

For objects, properties defines a schema for each named key, while required lists the keys that must be present. Note carefully: required only checks presence, not the value — a required field can still be null unless you also constrain its type. Setting additionalProperties to false rejects any key not listed under properties, which is useful for strict API contracts where unexpected fields should be flagged.

{
  "type": "object",
  "properties": {
    "username": { "type": "string" },
    "email":    { "type": "string" },
    "age":      { "type": "integer" }
  },
  "required": ["username", "email"],
  "additionalProperties": false
}

String Constraints

Strings support several keywords beyond just type. minLength and maxLength set character-count bounds. The pattern keyword accepts a regular expression (ECMA 262 dialect) that the string must match. The enum keyword restricts the value to one of a fixed list, and format declares a semantic format like "email", "uri", "date", or "uuid" — though note that format validation is optional in many implementations and must be explicitly enabled.

{
  "username": {
    "type": "string",
    "minLength": 3,
    "maxLength": 30,
    "pattern": "^[a-zA-Z0-9_-]+$"
  },
  "email": {
    "type": "string",
    "format": "email"
  },
  "role": {
    "type": "string",
    "enum": ["admin", "editor", "viewer"]
  }
}

Number Constraints

Numbers can be bounded with minimum, maximum, exclusiveMinimum, and exclusiveMaximum. The multipleOf keyword enforces divisibility — useful for things like pagination page sizes that must be multiples of 10 or currency amounts that must be multiples of 0.01.

{
  "age": {
    "type": "integer",
    "minimum": 13,
    "maximum": 120
  },
  "score": {
    "type": "number",
    "minimum": 0,
    "maximum": 100,
    "multipleOf": 0.5
  }
}

Array Validation

For arrays, the items keyword provides a schema that every element must satisfy. minItems and maxItems control array length, and uniqueItems: true requires that no two elements are identical — handy for tag lists or permission sets where duplicates make no sense.

{
  "tags": {
    "type": "array",
    "items": { "type": "string", "minLength": 1, "maxLength": 50 },
    "minItems": 1,
    "maxItems": 10,
    "uniqueItems": true
  }
}

Nested Schemas with $ref and $defs

Real schemas quickly grow complex. The $defs keyword (called definitions in older drafts) lets you define reusable sub-schemas in one place and reference them with $ref. This avoids copy-pasting the same address schema into every object that has a billing or shipping address.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "address": {
      "type": "object",
      "properties": {
        "street":  { "type": "string" },
        "city":    { "type": "string" },
        "country": { "type": "string", "minLength": 2, "maxLength": 2 }
      },
      "required": ["street", "city", "country"]
    }
  },
  "type": "object",
  "properties": {
    "billingAddress":  { "$ref": "#/$defs/address" },
    "shippingAddress": { "$ref": "#/$defs/address" }
  }
}

Complete Example: User Registration Payload

Here is a realistic schema for a user registration API endpoint. It combines type constraints, string validation, optional nested objects, and reusable definitions into a single document you could drop into a validator today.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/register.json",
  "title": "User Registration",
  "type": "object",
  "properties": {
    "username": {
      "type": "string",
      "minLength": 3,
      "maxLength": 30,
      "pattern": "^[a-zA-Z0-9_-]+$"
    },
    "email": {
      "type": "string",
      "format": "email",
      "maxLength": 254
    },
    "password": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128
    },
    "age": {
      "type": "integer",
      "minimum": 13
    },
    "newsletter": {
      "type": "boolean"
    },
    "interests": {
      "type": "array",
      "items": { "type": "string" },
      "maxItems": 20,
      "uniqueItems": true
    }
  },
  "required": ["username", "email", "password"],
  "additionalProperties": false
}

Validating with Ajv (JavaScript)

Ajv is the most widely used JSON Schema validator for JavaScript and Node.js. It compiles schemas to optimised validation functions, which means repeated validations on the same schema are very fast. Install it with npm install ajv, then:

import Ajv from "ajv";
import addFormats from "ajv-formats"; // for "email", "uri", etc.

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

const schema = {
  type: "object",
  properties: {
    username: { type: "string", minLength: 3 },
    email: { type: "string", format: "email" }
  },
  required: ["username", "email"]
};

const validate = ajv.compile(schema);

const data = { username: "alice", email: "alice@example.com" };
const valid = validate(data);

if (!valid) {
  console.error(validate.errors);
} else {
  console.log("Data is valid");
}

The validate.errors array contains structured error objects with a message, a JSON Pointer instancePath pointing to the failing field, and a keyword indicating which constraint was violated — making it straightforward to map errors back to form fields in a UI.

Validating with jsonschema (Python)

The jsonschema library is the standard choice in Python. Install it with pip install jsonschema. The simplest usage calls validate(), which raises a ValidationError on failure:

import json
from jsonschema import validate, ValidationError

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

payload = {"username": "alice", "email": "alice@example.com"}

try:
    validate(instance=payload, schema=schema)
    print("Valid")
except ValidationError as e:
    print(f"Invalid: {e.message}")
    print(f"Field path: {list(e.absolute_path)}")

For validating many documents against the same schema, create a jsonschema.Draft202012Validator instance once and call its validate() method repeatedly to avoid re-parsing the schema on every call.

Testing Your Schemas

Before wiring a schema into application code, it is worth testing it manually with sample payloads — both valid ones and deliberately broken ones that should fail. Paste your JSON into the JSON Formatter to ensure it is well-formed before feeding it to a validator. Catching a missing comma in your test data first saves confusion about whether the schema or the data is wrong.

JSON Schema is a powerful contract mechanism that moves validation logic out of imperative code and into a declarative, language-agnostic document. Once you have a schema, you can share it with API consumers, generate documentation from it, use it in CI to gate malformed requests, and even derive TypeScript types from it with tools like json-schema-to-typescript.