top of page

OpenAI Structured Outputs vs JSON Mode: Schema Enforcement, Reliability, and Failure Modes

  • 52 minutes ago
  • 6 min read

JSON mode guarantees the model returns valid JSON. It says nothing about whether that JSON has the fields you asked for — and that gap is exactly what Structured Outputs was built to close.

........

  • JSON mode (response_format: {type: "json_object"}) only guarantees parseable JSON; nothing stops a missing field or a hallucinated enum value.

  • Structured Outputs (response_format: {type: "json_schema"}) constrains token generation itself, so the model's output can't deviate from the schema you supply.

  • OpenAI's own evaluations put Structured Outputs at 100% schema compliance, against roughly 86% for function calling and lower still for plain JSON mode.

  • Structured Outputs only supports a subset of JSON Schema: no minLength/maxLength, a five-level nesting cap, and required fields on every property.

  • The first request against a new schema costs extra latency — OpenAI compiles it into a constrained grammar and caches the result for reuse.

··········

WHAT JSON MODE ACTUALLY GUARANTEES.

JSON mode's contract is narrower than most people assume when they first turn it on.

Setting response_format to {"type": "json_object"} tells the model to return syntactically valid JSON — parseable, balanced braces, correctly quoted strings.

What it does not do is check that JSON against any shape. A required field can go missing. An enum can come back with a value that was never in the list. Nesting can be shallower or deeper than expected.

OpenAI's own framing at launch was direct about the gap: JSON mode improves reliability for generating valid JSON, but it does not guarantee the response conforms to a particular schema.

In practice that means JSON mode still needs a validation layer downstream — the model won't hand back garbled text, but it can hand back well-formed JSON your application can't actually use.

··········

WHAT STRUCTURED OUTPUTS ADDS: CONSTRAINED DECODING.

Structured Outputs doesn't validate the response after the fact — it restricts what the model is allowed to generate at each token.

The mechanism is constrained decoding: at every step of generation, the system tracks which tokens would keep the output valid against the schema and masks out every token that wouldn't.

If a field is defined as an enum with only "approved" and "rejected" as options, the model is structurally incapable of producing "maybe" in that slot — not discouraged from it, incapable of it.

That's the core difference from JSON mode: JSON mode nudges the model toward valid syntax through training and prompting; Structured Outputs removes the invalid paths from the token space entirely.

You supply the schema as JSON Schema directly, or through typed helpers — Pydantic models in Python, Zod schemas in JavaScript — and the SDK translates that into the same constraint underneath.

··········

THE RELIABILITY NUMBERS.

The gap between the two approaches isn't marginal in OpenAI's own published evaluations.

Structured Outputs measured 100% schema compliance in OpenAI's evaluations. Function calling, which applies a related but separate constraint mechanism, measured roughly 86%. Raw JSON mode with no schema attached scored lower still.

A separate breakdown of unconstrained, prompt-only JSON generation put its failure rate at roughly 15-25% depending on prompt complexity — meaning one in five or six responses simply isn't parseable as-is, before you even get to whether the fields are right.

··········

Compliance by approach (OpenAI's published evaluations)

Approach

Schema compliance

What can still go wrong

Raw text / prompt-only JSON

~75-85% parseable

Malformed JSON, missing fields, wrong types

JSON mode

Valid JSON, schema not checked

Missing fields, invalid enums, wrong shape

Function calling

~86%

Occasional schema drift

Structured Outputs

100%

Refusals, latency, unsupported schema features

··········

THE SCHEMA SUBSET STRUCTURED OUTPUTS ACTUALLY SUPPORTS.

The 100% figure comes with a real constraint: Structured Outputs enforces only a subset of JSON Schema, not the full specification.

Nesting is capped at five levels deep. Total string length across property names and enum values has a limit. Every property in strict mode has to be marked required — optional fields are handled by allowing null as a type rather than by omitting them.

A handful of common JSON Schema keywords aren't supported at all, including minLength and maxLength for strings — constraints that plenty of existing schemas rely on and that need to move into validation logic instead.

Recursive schemas work through $ref in principle but get fragile fast with real recursion depth. For tree-shaped data, the practical workaround is flattening to a list with parent-ID references rather than nesting children — easier to validate and easier to render besides.

Root objects also can't use anyOf, and Azure's implementation of the same feature layers on a few of its own additional restrictions beyond OpenAI's base list — worth checking separately if you're on Azure OpenAI rather than the direct API.

··········

FAILURE MODES THAT STILL HAPPEN.

100% schema compliance is not the same claim as 100% reliability, and the two get conflated more often than they should.

Refusals are the first real failure path: if the model judges a request unsafe, it can decline to comply with the schema entirely, and the response carries a refusal flag set to true instead of the expected structure. Any production pipeline needs a code path that checks for this explicitly rather than assuming every response fits the schema.

Schema-valid but useless content is the second: Structured Outputs guarantees shape, not substance. A response can have every required field, correctly typed, with an enum value that's technically valid and semantically wrong for the input. Constrained decoding has no opinion about whether the content is actually good.

Latency is the third, and it's the least predictable of the three: independent testing has reported occasional spikes as large as a 20x increase in response time under Structured Outputs, on top of the smaller, expected first-request compilation cost.

··········

THE FIRST-REQUEST LATENCY TAX.

A new schema costs more the first time it's used, and that cost is designed to disappear afterward.

OpenAI compiles a new schema into a constrained grammar on first use, then caches that compiled artifact for fast reuse. Typical schemas process in under ten seconds on that first request; more complex ones can take up to a minute.

A separate, more granular estimate from independent testing puts the actual compilation overhead at 200-400 milliseconds for a first call against a new schema, rather than the multi-second worst case OpenAI's own guidance describes for the heaviest schemas.

Either way, the operational implication is the same: reusing a stable schema across many requests is fast, and generating a slightly different schema on every call — dynamic field sets, per-request customization — pays that compilation cost repeatedly instead of once.

··········

HOW OPENAI COMPARES TO ANTHROPIC AND GOOGLE HERE.

Every major provider has landed on some version of schema enforcement, but the API shape differs enough that code doesn't port directly between them.

OpenAI exposes it directly as response_format with type json_schema, available across the Chat Completions API, the Responses API, the Assistants API, fine-tuning, and batch.

Anthropic has no equivalent standalone JSON mode. Instead, Claude enforces schema-valid output by defining a tool with an input_schema and forcing its use through tool_choice — schema compliance rides on the tool-calling mechanism rather than a separate response format.

Google's Gemini takes a third approach: setting responseMimeType to application/json requests JSON output, and a separate, model-dependent responseSchema field constrains it further where supported.

The practical consequence for anyone building against more than one provider: schema-enforcement code is not a drop-in abstraction across OpenAI, Anthropic, and Gemini, even though all three are solving the same underlying problem.

··········

WHEN TO USE WHICH.

The choice mostly comes down to whether the model in use supports Structured Outputs at all, and how much the schema needs to bend at runtime.

Default to Structured Outputs whenever the model supports it. OpenAI's own guidance is unambiguous on this: use Structured Outputs instead of JSON mode whenever it's available, since it's strictly the stronger guarantee for no added token cost — the constraint applies during generation at standard per-token pricing.

Fall back to JSON mode for models that predate Structured Outputs support (OpenAI's own cutoff is gpt-4o-2024-08-06 and gpt-4o-mini onward), or for cross-provider code paths that need to run against a model without native schema enforcement and are willing to validate the response manually afterward.

Reach for function calling instead of either when the goal isn't extracting data but invoking a tool with structured parameters — the two mechanisms overlap in enforcement style but solve different problems.

Whichever mechanism is in use, treat refusals as a first-class branch in the code, not an edge case: log them, and have a defined fallback — a degraded response, a retry with a modified prompt, or escalation to a human — rather than assuming the refusal field will stay false.

And keep schemas as stable as the workload allows: a fixed schema reused across thousands of calls pays the compilation cost once; a schema that mutates per request pays it every time.

··········

·····

FOLLOW US FOR MORE.

·····

·····

DATA STUDIOS

·····

Recent Posts

See All
bottom of page