Structured Outputs in Practice
Get guaranteed-valid JSON from a model with schema-constrained decoding, parse it into typed objects, and know the limits of what the guarantee covers.
Most AI features don't end with text on a screen. They end with your code doing something: setting a ticket's priority, filling a form, saving a row, choosing which component to render. For that you need output a program can trust, and "please respond in JSON" doesn't give you that. It works in the demo, and then one response in a few hundred arrives wrapped in a markdown fence, or with a friendly sentence in front, or with "priority": "Urgent!!", and your parser throws in production.
This lesson shows you how to get JSON that is guaranteed to match a schema, parse it into typed TypeScript objects, and handle the cases where the guarantee doesn't hold. Just as important, you'll learn exactly what the guarantee covers, so you don't mistake well-formed output for correct output.
Why "respond in JSON" breaks in production
Before structured outputs existed, builders got JSON by asking for it. The prompt said "Return only a JSON object with these keys," and the code ran JSON.parse and hoped. A language model generates text one token at a time, choosing likely continuations. A JSON instruction makes valid JSON likely. It doesn't make it certain.
The failures are familiar to anyone who has run this at scale. What matters for a product is what each one does downstream:
| What the model returned | What your product did |
|---|---|
| A friendly preamble, or the object wrapped in a markdown fence | JSON.parse threw, the triage worker crashed, and the ticket sat unrouted |
No priority key on an ambiguous ticket | undefined sorted to the bottom of the queue, so an urgent ticket waited a day |
"priority": "medium-high" when you allowed four values | The queue filter grew a fifth option nobody could select |
An object cut off mid-string at max_tokens | A retry loop paid for three more calls with the same limit and got the same result |
Teams used to stack defenses: regexes to strip fences, retry loops, "repair" libraries that fix trailing commas. Some of that is still useful (you'll build a proper repair loop in Validation, Retries, and Repair), but the base layer has changed. Every major provider now offers schema-constrained decoding, and it should be your default whenever code consumes the output.