Topic: Learning notes

Structured Output: The AI Returned JSON. Now What?

Compare format prompts, JSON mode, and schema constraints. Use metadata checks, timestamps, and a token-estimation counterexample to see what the application still needs to validate.

“Return only JSON. No explanation.”

If the next step is JSON.parse(), an extra “Sure, here’s your data” can send that helpful response straight into catch. The reply is polite. The application is probably less appreciative.

Following Core Prompting Techniques, this note covers Sabrina Goldfarb’s Structured Output lesson. I’ll connect the requested format to the checks needed after receiving it, using meeting extraction and prompt-library metadata.

The prompts and expected JSON are original examples, not observed model responses. Date and field validation, along with the token-estimation counterexample, have executable checks.

Are we requesting a format or constraining generation?

The course uses structured output in the broad sense of requesting a predictable format. Fixed text fields, document templates, and code blocks can all make subsequent handling clearer. Uses include extraction, test cases, documentation, and batches of similar content.

When connecting an API, distinguish the mechanisms:

Approach What it constrains What the receiver still handles
Prompt instructions and demonstrations Guides the model toward a format Deviations, extra text, and parsing failures
JSON mode, where available Valid JSON, without a specific field structure Fields, types, and exceptions such as interrupted output
API schema-constrained output Generation structure within supported schema features Service exceptions, factual accuracy, and business rules

OpenAI’s guide distinguishes JSON mode from schema adherence. Anthropic’s guide describes constrained decoding and its supported scope. Pasting a schema into a prompt doesn’t activate an API’s constraint mechanism.

Agree on the data's shape so the receiver knows how to use each field. · Source: GIPHY

Make the extraction request concrete

Start with a small email-extraction prompt:

Extract dateText, timeText, location, and topic from the email below.
Include all four fields. Each value must be a string or null.
Use null for information not mentioned; do not fill it in yourself.
Preserve date and time wording instead of converting to timestamps.
Return only a JSON object, without Markdown fences or explanations.

Email: Let's meet tomorrow at three in the afternoon in Conference Room C
to discuss the new homepage.

The manually specified expected result is:

{
  "dateText": "tomorrow",
  "timeText": "three in the afternoon",
  "location": "Conference Room C",
  "topic": "new homepage"
}

This example deliberately keeps “tomorrow.” Resolving it into a timestamp requires the email’s reference date and applicable time zone. The date on which a program happens to run cannot silently supply that missing context.

Next, remove the location or topic and check for null. That separates format compliance from faithful extraction. Beautiful JSON containing a meeting room absent from the email still needs to be rejected.

Schemas name the fields; check who enforces the rules

The course then asks AI to generate metadata functionality for a prompt library, including trackModel, updateTimestamps, and estimateTokens. That specifies requirements for generated code; it doesn’t automatically enable runtime schema constraints.

I’ll take the model name and two timestamps as a smaller example:

{
  "model": "example-model-v1",
  "createdAt": "2026-10-19T09:00:00.000Z",
  "updatedAt": "2026-10-19T09:00:00.000Z"
}

A general JSON Schema for those fields could be:

{
  "type": "object",
  "properties": {
    "model": { "type": "string", "minLength": 1, "maxLength": 100 },
    "createdAt": { "type": "string", "format": "date-time" },
    "updatedAt": { "type": "string", "format": "date-time" }
  },
  "required": ["model", "createdAt", "updatedAt"],
  "additionalProperties": false
}

This describes required fields, types, and the extra-field policy. It doesn’t compare the timestamps or reject a model name made entirely of whitespace.

The JSON Schema specification bases date-time on RFC 3339; enforcing format depends on the validator and its configuration. APIs also support subsets of keywords. At the time of review, Anthropic’s raw API schema doesn’t support minLength or maxLength; receiving code must enforce them. This general schema isn’t a request configuration to paste unchanged into every API.

A timestamp needs to be a real date, too

The small example uses these rules:

Field Rule in this example
model Must contain non-whitespace text, with at most 100 Unicode code points; no fixed model enumeration
createdAt, updatedAt Real dates in fixed UTC millisecond notation, such as 2026-10-19T09:00:00.000Z
Ordering updatedAt must be at or after createdAt

This deliberately chooses a narrow timestamp representation. It doesn’t claim that every ISO 8601 or RFC 3339 timestamp looks this way. An offset such as +08:00 can represent a valid time, but this example requires callers to normalize it to UTC first.

Here is the date-checking excerpt from the download. data has already passed its object, field, and model-name checks:

for (const field of ["createdAt", "updatedAt"]) {
  const value = data[field];
  if (
    typeof value !== "string" ||
    !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) ||
    !Number.isFinite(Date.parse(value)) ||
    new Date(value).toISOString() !== value
  ) {
    throw new Error(`${field} must be a real date in UTC millisecond format`);
  }
}
if (Date.parse(data.updatedAt) < Date.parse(data.createdAt)) {
  throw new Error("updatedAt must be at or after createdAt");
}

The regular expression checks the shape. Parsing and converting back to the same representation catches date-shaped inputs such as 2027-02-30. Only then do we compare creation and update times; equality is allowed.

Writing JSON.parse(text) as Metadata doesn’t run those checks. The TypeScript handbook explains that assertions disappear at compilation. External data still needs runtime validation. Reuse the project’s existing validation tools when available.

A flexible model name lets records accommodate new versions. If that field chooses a model to actually call, supported-model and authorization checks remain a separate responsibility. Application-known facts, such as the model requested and creation time, should be recorded by the application rather than asking the model to guess its own biography.

Token estimates: check which end of the range is larger

The course’s token estimator uses this heuristic, multiplying both values by 1.3 for code:

min = 0.75 × word_count
max = 0.25 × character_count

Try a b c d: four whitespace-separated words and seven characters including spaces.

Calculation Result
min = 0.75 × 4 3
max = 0.25 × 7 1.75
After applying the code multiplier min = 3.9, max = 2.275

We haven’t contacted a model, and the “range” is already upside down. Sorting the two values makes the display look plausible, but doesn’t establish that the actual token count falls between them.

I’d retain 1.3 as a teaching heuristic, not a constant for every programming language, text, or tokenizer. For capacity and cost budgeting, use the relevant model’s tokenizer or provider counting interface, and check the complete request and actual usage.

The course maps high, medium, and low to green, yellow, and red, using 1,000 and 5,000 tokens as band boundaries. An implementation still needs to define which estimate is compared and where boundaries belong. These labels aren’t calibrated probabilities of accuracy. Keep the text labels visible so color isn’t the only signal.

After structural checks, verify the content and business rules too. Hold the approval stamp for a moment. · Source: GIPHY

Stop errors where they can be handled

When integrating a service, I’d check completion status before parsing a normal result:

Layer Example Handling direction
Service and completion status API errors, refusals, truncated output Handle the service response separately from normal data
JSON syntax Extra explanation, missing brackets Report a parsing failure
Structure and fields Missing fields, wrong types, disallowed extras Report validation failure
Content and business rules Earlier update time, an invented location Check evidence and application rules

Anthropic documents refusals and token limits as cases that may not match the schema. An HTTP 200 alone therefore isn’t enough to send the response straight to a database. If retries are appropriate, bound them and define when to stop. A format failure doesn’t justify retrying forever.

Likewise, conveniently formatted code still needs compilation, tests, and relevant security checks. Structured output helps specify a contract without performing those tasks for us.

Run the receiving-side checks

Download the complete validation example and run it with Node.js:

node structured-output-validation.mjs

It uses built-in Node.js assertions with no extra packages. Checks cover valid input, equal timestamps, leap days, invalid dates, reversed ordering, missing and extra fields, blank or overlong model names, and the token-estimation counterexample.

These checks passed locally and verify receiving-side behavior. Whether a model consistently extracts the correct information needs separate testing; this passing result cannot stand in for that evidence.

References and scope

Sources checked on 2026-10-19. This note uses original prompts, reduced metadata, and local checks rather than a verbatim course translation. Confirm API support against the model, endpoint, and SDK version you actually use.