Structured Outputs: Getting Data, Not Prose
Make models return reliable JSON that matches a schema, so agents can hand results to code, databases, and other agents without fragile parsing.
The moment an agent's output feeds into code, prose becomes a liability. Your script needs an invoice total, not "Sure! Based on the document, the total appears to be around €1,370." Your database needs a status of paid, not "Paid (I think)". Another agent needs a list of tasks, not three paragraphs describing them.
The obvious fix, asking the model to "reply in JSON", works most of the time. Most of the time is the problem: one malformed response in a thousand is enough to crash a pipeline at 3 a.m. Structured outputs solve this by constraining the model so its response is guaranteed to match a schema you define. In this lesson you'll learn why prompt-only JSON breaks, the three ways to get structured data from a model, how to make a schema-constrained call in TypeScript, how to design schemas a model fills well, and what a schema can't guarantee.
Why "reply in JSON" breaks
JSON (JavaScript Object Notation) is the curly-brace format most software uses to exchange data. If you ask a model for JSON in your prompt, you'll get something that looks right on the first ten tries. Run it ten thousand times and you'll meet every one of these:
| Failure | What you get | What it breaks |
|---|---|---|
| Chatty preamble | "Here's the JSON you asked for:" before the data | JSON.parse throws |
| Code fences | The JSON wrapped in triple backticks | JSON.parse throws |
| Invalid syntax | A trailing comma, an unescaped quote, a comment | JSON.parse throws |
| Missing field | No total because the document didn't show one clearly | Your code reads undefined |
| Invented value | "status": "partially paid" when you expected paid or unpaid | Your logic takes the wrong branch |
| Wrong type | "total": "€1,370.00" instead of the number 1370 | Arithmetic quietly fails |
| Truncation | The response stops halfway through an array | Invalid JSON, or silently incomplete data |
You can patch each one: strip code fences with a regular expression, retry on parse errors, coerce strings to numbers. Teams that do this end up with a growing pile of fragile cleanup code that still fails in new ways. The better approach is to stop the bad output from being generated at all.