"What shape is this JSON supposed to be?" is a question every API consumer eventually asks. JSON Schema is the answer — a way to describe, in JSON itself, exactly what a valid JSON document should look like: which fields are required, what type each one is, and what values are acceptable.

What problem it actually solves

Without a schema, "what does this API return?" is answered by reading documentation (which might be out of date), staring at an example response (which might not show every possible field), or just making a request and seeing what comes back. A schema turns that fuzzy, example-based understanding into something a machine can check automatically — you can validate a document against it and get a precise, actionable answer: valid, or not, with a specific reason why.

A first example

Say you have this JSON, describing a product:

{
  "id": 101,
  "name": "Wireless Mouse",
  "price": 29.99,
  "inStock": true
}

Here's a JSON Schema that describes it:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "id": { "type": "integer" },
    "name": { "type": "string" },
    "price": { "type": "number" },
    "inStock": { "type": "boolean" }
  },
  "required": ["id", "name", "price", "inStock"]
}

Read it almost like plain English: this is an object, it has four properties with these specific types, and all four are required. A validator library takes this schema plus a JSON document and tells you whether the document satisfies it.

The building blocks

Adding constraints beyond just type

Type alone often isn't specific enough. JSON Schema lets you add real constraints on top:

{
  "type": "object",
  "properties": {
    "age": { "type": "integer", "minimum": 0, "maximum": 130 },
    "email": { "type": "string", "format": "email" },
    "username": { "type": "string", "minLength": 3, "maxLength": 20 }
  }
}

Now age isn't just "any integer" — it has to be a sane human age. username has to be a reasonable length. This is the difference between a schema that just checks "is this the right shape" and one that actually enforces meaningful business rules.

Nested objects and arrays

A schema can describe arbitrarily nested structures. Here's one for an order with a list of line items:

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

Notice how items (the array-of-objects field) has its own nested type, properties, and required — schemas compose naturally, which is what lets them describe realistically complex, deeply nested API payloads.

What people actually use it for

A realistic way to get started

Writing a schema by hand from scratch is tedious and error-prone for anything beyond a small object. A much more practical starting point is to take a real, representative example of your data and infer a schema from it automatically, then refine the result — loosen fields that were only sometimes present in your sample, add constraints (like minimum or format) that the raw data alone can't tell you, and remove any required entries you know are actually optional across other real records.

Frequently Asked Questions

No — the practical approach most people use is to infer a starting schema from a real example of the data, then hand-edit it to loosen fields that are actually optional and add constraints (like minimum, maxLength, or enum) that can't be inferred from a single example.
TypeScript interfaces are checked at compile time, describing the shape your code expects, but they disappear at runtime — they can't validate data your program receives from the outside world. JSON Schema is checked at runtime against actual data, making it the right tool for validating API requests, config files, or any data coming from outside your codebase.
Draft-07 is a widely supported, stable choice with broad validator library support across most languages. Newer drafts (2019-09, 2020-12) add features, but Draft-07 remains a safe, compatible default unless you specifically need something a newer draft added.
Yes, using the pattern keyword with a regular expression, or the built-in format keyword for common patterns like email or date-time (though format validation support varies somewhat by validator library, so check what your specific library enforces).