Structured Outputs
Intermediate 12 min read Structured Outputs

JSON Schemas

The contract, in declarative form

A JSON Schema declaratively describes allowed output: types, required fields, enums, nested objects. Schemas are the contract LLMs validate against — and over-complex schemas break them.

json schemaschematypesrequiredenumvalidation
Before we start

What you will learn

  • Write a JSON Schema for a model output
  • Limit schema complexity that causes failures
  • Version schemas as code
Introduction

What is this?

A JSON Schema specifies the expected shape with types, required lists, enums, strings formats and nested structures. LLM platforms accept schemas as constraints (structured output) or guidance (prompt-injected). The pragmatic rule: the simpler the schema, the higher the compliance — enormous required-field trees are where models stumble.

Motivation

Why do you need it?

  • The schema is the single source of truth for every consumer.
  • Enums and types catch drift before it reaches the UI or DB.
  • Validators and SDKs consume the same schema — one definition, many uses.
  • Schema complexity directly trades against model compliance rate.
Think about it

Mental model

Imagine

A customs declaration form

Think of

JSON Schema

The form says 'name: text, quantity: integer, value: number, country: from a list'. The traveller (model) fills it; a bad form either gets rejected at the window or — if the window accepts anything — chaos downstream. Clear fields, few of them, guarantee a fast pass.

Mechanics

How it works

  • Define top-level type 'object' with properties, required, additionalProperties.
  • Use type, enum, format, minItems/uniqueItems for tight control.
  • Nest objects/arrays modestly; avoid deep recursion.
  • Platforms map schemas to constrained decoding (structured mode).
  • Validators everywhere: server-side parse, UI type guards, test fixtures.

One schema, many enforcers.

Schema
Model
Validators
Consumers
Hands-on

Examples

A compact schema

A compact schema
const schema = {
type: "object",
properties: {
intent: { type: "string", enum: ["refund", "complaint", "question"] },
confidence: { type: "number", minimum: 0, maximum: 1 },
entities: {
type: "array",
items: { type: "string" },
maxItems: 10,
},
},
required: ["intent", "confidence", "entities"],
additionalProperties: false,
};

Three fields, tight enums, bounded arrays — model-compliant and validator-happy.

Real world

In production

  • Platform structured mode consumes this exact object.
  • API responses are typed from the same schema via codegen.
  • Eval fixtures assert output conforms to a schema version.
Guidelines

Best practices

  • Minimize required fields; give notable fields sensible defaults.
  • Bound enumerations (intent lists), arrays, and string lengths.
  • Set additionalProperties: false where strictness matters.
  • Version schemas in the repo with consumers and fixtures together.
Watch out

Common mistakes

  • Deeply nested required-object graphs the model never completes.
  • Enums that don't cover realistic model outputs (validation purgatory).
  • Schemas drifting from the validator and SDK (three truths).
Root cause analysis

Common issues

Error
Model violates the enum
Root cause

Enum too narrow or prompt not showing options

Fix

Widen enum or inject the allowed values into the prompt.

Error
Deep required nesting always partial
Root cause

Generative step can't plan the whole tree

Fix

Split into separate calls or flatten objects.

When things break

Debugging tips

  • Compliance falls with complexity → flatten, drop rarely-needed requireds.
  • Enum values mismatch real outputs → log actual values, extend the enum.
  • Schema drift between files → derive TS types and validators from one schema.
Interviews

Interview checkpoint

Test yourself

Quick quiz

0/2
1

A JSON Schema declares…

2

Deeper and larger schemas always give better output.

Go deeper

Official documentation