DEV Community

Snappy Tools
Snappy Tools

Posted on • Originally published at snappytools.app

JSON Schema Explained: How to Validate Your API Data in 10 Minutes

If your API has ever received {"age": "twenty-three"} when it expected a number, you already know why JSON Schema exists.

JSON Schema lets you describe the exact shape of valid JSON data — types, required fields, string patterns, numeric ranges, and much more — and then validate any JSON document against that description. It's the schema language baked into OpenAPI (Swagger), used by Ajv (the most popular JS validator), and understood by almost every modern API toolchain.

This guide covers the essentials: what JSON Schema is, how to write one, and which keywords you'll use 90% of the time.

What Is JSON Schema?

A JSON Schema is itself a JSON document. It describes what another JSON document must look like to be considered valid.

{
  "type": "object",
  "required": ["name", "email"],
  "properties": {
    "name": { "type": "string", "minLength": 1 },
    "email": { "type": "string", "format": "email" }
  }
}
Enter fullscreen mode Exit fullscreen mode

This schema says: "I expect an object with at least name (a non-empty string) and email (a valid email address)." Any JSON that satisfies those rules is valid against this schema.

Draft Versions — Which One Should You Use?

JSON Schema has several published drafts. The one you'll encounter most often in production systems is draft-07:

  • Used by OpenAPI 3.0 (the Swagger standard)
  • Supported by Ajv (JavaScript), jsonschema (Python), json-schema-validator (Java)
  • Introduced if/then/else conditional validation
  • The most widely deployed version as of 2026

The current specification is draft 2020-12, but draft-07 still dominates tooling. Stick with draft-07 unless you have a specific reason to upgrade.

The Keywords You'll Use Most

type

Specifies the JSON type of the value. Valid values: string, number, integer, boolean, null, array, object.

{ "type": "integer" }
{ "type": ["string", "null"] }
Enter fullscreen mode Exit fullscreen mode

An array of types means the value can be any of those types — useful for nullable fields.

properties and required

properties defines the expected fields in an object and their schemas. required lists which fields must be present.

{
  "type": "object",
  "required": ["id", "name"],
  "properties": {
    "id":   { "type": "integer" },
    "name": { "type": "string" },
    "bio":  { "type": "string" }
  }
}
Enter fullscreen mode Exit fullscreen mode

Here, id and name are required; bio is optional but must be a string if present.

additionalProperties

Controls whether an object can have fields not listed in properties.

  • "additionalProperties": false — reject any unknown fields
  • "additionalProperties": { "type": "string" } — allow extra fields but only if they're strings

For strict API validation where unknown fields should be rejected, false is the right choice.

enum and const

enum restricts a value to a fixed set of options. const restricts it to exactly one value.

{ "type": "string", "enum": ["admin", "editor", "viewer"] }
{ "const": "published" }
Enter fullscreen mode Exit fullscreen mode

String Constraints

{
  "type": "string",
  "minLength": 3,
  "maxLength": 50,
  "pattern": "^[a-z0-9_]+$",
  "format": "email"
}
Enter fullscreen mode Exit fullscreen mode
  • minLength / maxLength — character count bounds
  • pattern — must match this regular expression
  • format — semantic type: email, uri, date, date-time, ipv4, hostname

Note: format is technically optional in validators — some enforce it, some treat it as annotation only.

Number Constraints

{
  "type": "number",
  "minimum": 0,
  "maximum": 100,
  "multipleOf": 5
}
Enter fullscreen mode Exit fullscreen mode

For exclusive bounds (i.e., strictly greater than / less than), use exclusiveMinimum and exclusiveMaximum. In draft-07, these accept numeric values directly: "exclusiveMinimum": 0 means > 0, not ≥ 0.

Array Constraints

{
  "type": "array",
  "items": { "type": "string" },
  "minItems": 1,
  "maxItems": 10,
  "uniqueItems": true
}
Enter fullscreen mode Exit fullscreen mode

When items is a single schema, every element in the array must match it. For tuple validation (different schemas per position), set items to an array of schemas.

allOf, anyOf, oneOf

These combine multiple schemas:

  • allOf — data must be valid against all schemas (logical AND)
  • anyOf — data must be valid against at least one schema (logical OR)
  • oneOf — data must be valid against exactly one schema
{
  "oneOf": [
    { "properties": { "type": { "const": "circle" } }, "required": ["radius"] },
    { "properties": { "type": { "const": "rect" } }, "required": ["width", "height"] }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This is the JSON Schema way to write a discriminated union.

$ref and $defs

Reuse schema definitions instead of duplicating them:

{
  "$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" }
  }
}
Enter fullscreen mode Exit fullscreen mode

$defs (or definitions in older schemas) holds reusable definitions. $ref points to them using a JSON Pointer: #/$defs/TypeName.

if / then / else

Draft-07's conditional validation. If the data matches the if schema, it must also satisfy then. If it doesn't match if, it must satisfy else (if provided).

{
  "if":   { "properties": { "plan": { "const": "paid" } }, "required": ["plan"] },
  "then": { "required": ["paymentMethod"] }
}
Enter fullscreen mode Exit fullscreen mode

This requires paymentMethod only when plan is "paid".

Validate Your Schema Right Now

You can test any of the examples above — paste the schema on the left, paste some JSON on the right, and see clear path-based error messages instantly. No signup, no server upload, 100% in your browser:

👉 JSON Schema Validator — SnappyTools

Error messages show the exact JSON path where validation failed (e.g. $.user.address.zip), which makes debugging far faster than generic "invalid payload" errors.

Validating in Code

JavaScript (Node.js / browser):

npm install ajv ajv-formats
Enter fullscreen mode Exit fullscreen mode
import Ajv from 'ajv';
import addFormats from 'ajv-formats';

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

const schema = { /* your schema */ };
const validate = ajv.compile(schema);

if (!validate(data)) {
  console.error(validate.errors);
}
Enter fullscreen mode Exit fullscreen mode

Python:

pip install jsonschema
Enter fullscreen mode Exit fullscreen mode
from jsonschema import validate, ValidationError

try:
    validate(instance=data, schema=schema)
    print("Valid!")
except ValidationError as e:
    print(e.message, list(e.absolute_path))
Enter fullscreen mode Exit fullscreen mode

Both libraries implement draft-07 and use the same schema documents — so schemas you write and test in the browser validator work identically in production.

Key Takeaways

  • JSON Schema describes the shape and constraints of valid JSON data
  • Draft-07 is the most widely deployed version — it's what OpenAPI 3.0 uses
  • Essential keywords: type, properties, required, additionalProperties, enum, $ref, allOf/anyOf/oneOf, if/then/else
  • Validate immediately in the browser: snappytools.app/json-schema-validator
  • Use Ajv (JS) or jsonschema (Python) for production validation — same schemas, no rewriting

Built something that produces JSON? Add schema validation before it hits production — it takes 10 minutes to write a basic schema and it will save you hours of debugging.

Top comments (0)