Back
intermediate
Application Reliability

Structured Outputs with JSON Schema

Build AI features that return validated JSON instead of fragile freeform text

24 min read· structured outputs· json schema· function calling· validation

Structured Outputs with JSON Schema

Production AI systems rarely depend on freeform prose. They ask the model for a typed object that downstream code can validate.

Structured outputs are different from "please return JSON." A schema tells the model and your application exactly which fields, types, and constraints are allowed.

Why structured outputs matter

Freeform model output is fine for chat. It is risky for automation.

NeedFreeform textStructured output
Send data to codebrittle parsingvalidated fields
Detect refusalambiguousexplicit branch
Retry safelyhardschema-aware
Store in databasemanual cleanuptyped insert
Test regressionssubjectivemeasurable

Example schema

json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["priority", "summary", "actions"],
  "properties": {
    "priority": { "type": "string", "enum": ["low", "medium", "high"] },
    "summary": { "type": "string" },
    "actions": {
      "type": "array",
      "items": { "type": "string" }
    }
  }
}

Function calling vs response schema

Use function/tool calling when the model should decide to call code.

Use response schemas when the model should answer in a typed shape.

text
Need weather from API?      -> tool call
Need extracted invoice JSON? -> response schema
Need both?                  -> tool call, then structured final answer

Validation still belongs in your app

Even when the model supports schema-constrained output, your application should:

  • validate the returned object
  • reject unknown fields
  • enforce business rules
  • handle refusals explicitly
  • log schema failures for evals
  • never execute tool arguments without authorization

Pattern: parse, validate, act

ts
type Ticket = {
  priority: "low" | "medium" | "high";
  summary: string;
  actions: string[];
};

function handleTicket(ticket: Ticket) {
  if (ticket.priority === "high") {
    // escalate through normal application permissions
  }
}

Common pitfalls

  • asking for JSON in the prompt but not using a schema
  • allowing additional properties by default
  • accepting strings where numbers/enums are expected
  • treating structured output as trusted user input
  • retrying forever when validation fails

Knowledge check

Q1: Why is a JSON schema better than "return JSON"?
It defines allowed fields, types, required properties, and constraints.

Q2: Should validated model output be trusted automatically?
No. It is well-shaped data, not necessarily correct or authorized data.