Structured Output: Getting Reliable JSON from an LLM
Asking a model to "reply in JSON" works most of the time. Production code needs it to work every time. Structured outputs constrain the response to a JSON Schema, so the result always parses.
TypeScript with Zod
import Anthropic from '@anthropic-ai/sdk';
import { z } from 'zod';
import { zodOutputFormat } from '@anthropic-ai/sdk/helpers/zod';
const Ticket = z.object({
category: z.enum(['billing', 'bug', 'feature', 'other']),
priority: z.enum(['low', 'medium', 'high']),
summary: z.string(),
customer_email: z.string().nullable(),
});
const client = new Anthropic();
const response = await client.messages.parse({
model: 'claude-opus-5-5',
max_tokens: 16000,
messages: [{ role: 'user', content: `Classify this ticket:\n\n${emailBody}` }],
output_config: { format: zodOutputFormat(Ticket) },
});
const ticket = response.parsed_output; // typed as z.infer<typeof Ticket>, or null
if (ticket) await saveTicket(ticket);
Python with Pydantic
from typing import Literal, Optional
from pydantic import BaseModel
import anthropic
class Ticket(BaseModel):
category: Literal["billing", "bug", "feature", "other"]
priority: Literal["low", "medium", "high"]
summary: str
customer_email: Optional[str]
client = anthropic.Anthropic()
response = client.messages.parse(
model="claude-opus-5-5",
max_tokens=16000,
messages=[{"role": "user", "content": f"Classify this ticket:\n\n{email_body}"}],
output_format=Ticket,
)
ticket = response.parsed_output # a validated Ticket instance
Raw JSON Schema
Without a helper library, pass the schema yourself in output_config.format and parse the text block:
const response = await client.messages.create({
model: 'claude-opus-5-5',
max_tokens: 16000,
messages: [{ role: 'user', content: 'Extract the city and country from: "I live in Pune, India."' }],
output_config: {
format: {
type: 'json_schema',
schema: {
type: 'object',
properties: { city: { type: 'string' }, country: { type: 'string' } },
required: ['city', 'country'],
additionalProperties: false,
},
},
},
});
const text = response.content.find((b) => b.type === 'text');
const place = JSON.parse(text.text);
Schema design tips
- Use
enumfor categories so you never get "Bug", "bug " and "defect" for the same thing. - Use nullable fields for data that might be missing instead of letting the model invent values.
- Add a short
descriptionto fields whose meaning isn't obvious from the name. - Keep schemas flat where you can; deeply nested schemas are harder to fill correctly.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.