Structured outputs vs JSON mode is a choice about the contract between a language model and your application. JSON mode targets valid JSON. Structured outputs add adherence to a supported schema when correctly configured for a compatible model and API. Neither makes every extracted value factually correct.
That distinction matters when an answer feeds a database, form, or workflow. A response can parse successfully yet omit a required field, use the wrong type, or report a number that never appeared in the source. Build separate checks for syntax, structure, and meaning.
Structured outputs vs JSON mode: three layers to check
Syntax: can a JSON parser read the completed response? Missing quotation marks or an unfinished object fail this layer. Valid JSON is a serialization property; it does not define the fields your application expects.
Schema: does the parsed value have the required properties and permitted types? A contract might require a ticket ID string, a category from a fixed list, and a nullable integer. That is more specific than “return JSON.”
Meaning: do those values accurately represent the source and satisfy your business rules? A category can be allowed by the schema and still be the wrong classification.
OpenAI’s structured-output guide distinguishes JSON mode from schema adherence and documents exceptional outcomes such as refusals and incomplete responses. Treat the provider’s documented behavior as a contract with conditions, rather than interpreting the feature name as an unconditional guarantee.
Follow one fictional support-ticket example
Suppose your application receives this invented message: “Ticket T-104: I was charged twice for the same order. Please review the duplicate payment.” You want to extract three fields: ticket_id, category, and reported_charge_count. The allowed categories are billing, delivery, and other.
One response might be {"ticket":"T-104","category":"payment problem","reported_charge_count":"2"}. It is valid JSON, but the field name, category, and number type do not match the proposed contract. A parser alone will accept it.
A structurally suitable response would be {"ticket_id":"T-104","category":"billing","reported_charge_count":2}. That is consistent with the example message. However, a response using the same keys and types with a count of 3 would still be structurally valid. Your semantic check must catch the unsupported number.
This is a worked illustration, not the result of a model benchmark. It shows exactly which failure each validation layer can detect.
Define a contract before choosing the output feature
Start with the consuming application. Decide which properties are mandatory, what each one means, and how to represent missing information. For the ticket example, reported_charge_count should describe what the customer reported. It should not imply that the payment system has verified those charges.
The JSON Schema object reference explains properties, required fields, and additional-property rules. Merely listing a property does not make it required in ordinary JSON Schema. Decide those rules explicitly rather than relying on assumptions from a programming-language type.
Use a finite set for a genuinely bounded category. JSON Schema’s enum documentation describes how to restrict values to an allowed list. Include a reasonable fallback category if your task needs one. Forcing every unrelated message into billing or delivery creates a bad contract even if the model follows it perfectly.
Choose a deliberate missing-value policy. An unknown count might be null, while a genuine count of zero means something different. Document that distinction for both the prompt writer and the developer consuming the result.
Check the provider’s supported schema subset
Do not assume that an arbitrary JSON Schema accepted by your local validator is supported by every model endpoint. Read the provider’s restrictions for the exact model and API you use, including supported keywords and complexity limits.
Google’s Gemini structured-output documentation describes schema-based generation and examples using typed SDK definitions. Those implementation options can help keep an application’s data model aligned with its generation contract. They do not remove the need to inspect the supported features for your chosen configuration.
Keep one authoritative definition when practical, and generate related application types from it. If you maintain a schema and a separate interface by hand, add a change-review check so a renamed field does not silently break downstream code. Version the contract when a consumer depends on its current shape.
Handle refusal and incomplete output before parsing
A robust response path should first establish whether the request completed in the expected way. A refusal, interrupted stream, network error, or output-limit condition is not an ordinary extracted ticket. Route it to an explicit handling path.
- Inspect the API’s completion status and documented refusal signal.
- Wait for the complete response before committing a record that requires a whole object.
- Parse the expected output only after identifying a usable completion.
- Validate it locally against the application’s contract.
- Check the extracted values against the source and business rules.
- Commit the result only after those checks pass.
This order prevents a common mistake: treating a partial stream as finished data because the first fields look plausible. If a user interface displays incremental fields, keep that provisional display separate from the final record used to trigger an action.
Use retries with a reason and a limit
A retry can help after a transient service failure or a correctable validation problem. It should have a defined budget and a clear reason. Repeating the same request indefinitely can consume time and money without resolving an ambiguous source.
For the ticket example, a missing identifier should lead to clarification or review if the source never supplied one. Asking again should not pressure the model to invent an ID. Give the workflow a path for insufficient information instead of demanding a populated value at any cost.
Also separate extraction from side effects. Retrying an extraction should not automatically resend an email or create another refund request. Use application-level duplicate prevention when a later step can create an external change. Schema adherence does not provide that protection.
Test semantic correctness with an evaluation set
Create a small, representative collection of inputs with expected outcomes. Include clear cases, missing values, unrelated text, conflicting numbers, and messages that contain instructions your extractor should ignore. Record what counts as a correct field and when the system should abstain.
Track structural failures separately from wrong values. A decline in parsing errors can coexist with poor classification accuracy. Desinance’s AI evaluation checklist provides a framework for repeatable task checks. For hostile instructions embedded in source material, also review the prompt-injection guide.
Choose structured outputs when your application needs a predictable contract and the provider supports the schema you require. If you must use JSON mode, keep explicit schema validation and bounded recovery paths. In either case, the final acceptance decision belongs to the application: a well-formed object is the beginning of validation, not the end.

