WeMAIdeStart a project ↗

WEMAIDE / INSIGHTS / ENGINEERING

Structured outputs vs JSON mode: from valid JSON to valid application data

Compare JSON mode, schema-constrained output and tool calling. Run an offline invoice example that separates parsing, shape checks and business validation.

Engineering11 min read
Application checks surround the result of an AI step

Use JSON mode when the supported API needs to return parseable JSON. Use schema-constrained structured output when the application needs a particular shape. Use tool calling when the model proposes a supported function and its arguments. Then validate the result against the current business record: a correctly typed amount can still belong to the wrong invoice.

These names do not imply identical capabilities across providers or models. Check the selected API's supported schema subset, completion states and tool execution rules. This guide compares the boundaries and supplies an offline validator. It does not benchmark models or claim that a live model produced the sample outputs.

Compare the contract you actually need

Output shape and permission to act are separate decisions
MechanismUseful contractWhat still needs application checks
JSON modeJSON syntax under the selected provider's documented conditionsRequired fields, types, values and completion outcome
Schema-constrained structured outputThe supported shape and constraints for a successful responseBusiness meaning, unsupported schema rules and exceptional outcomes
Tool callingA proposed function and arguments; strictness depends on the API/configurationAllowed tool, user permissions, arguments, confirmation and execution result

Tool calling and structured output are not mutually exclusive. An API can support schema constraints for tool arguments as well as for the final response. Selecting a tool does not by itself execute the external action. Your application controls which operations exist and whether the proposed operation is permitted now.

Use one invoice to separate three different failures

Imagine an extraction step for synthetic invoice SYN-42. The server expects USD and a draft total no higher than 50,000 minor units before additional review. Those are example rules for this dataset, not universal accounting limits. The output contract contains an invoice reference, currency, subtotal, tax and total. For this USD-only exercise, 12,000 minor units means $120.00; do not assume every currency has two decimal places.

Synthetic draft
{
  "invoice_id": "SYN-42",
  "currency": "USD",
  "subtotal_minor": 10000,
  "tax_minor": 2000,
  "total_minor": 12000
}
The same task can fail at a different boundary
Candidate resultParsing / shapeApplication decision
An unfinished JSON objectCannot parseparse-review; retain a diagnostic outcome
total_minor is the string 12000Valid JSON; wrong field typeschema-review
Identical fields with currency EURValid JSON and shapebusiness-review: currency_mismatch
USD total 13000 against subtotal 10000 plus tax 2000Valid JSON and shapebusiness-review: arithmetic_mismatch
All sample rules passValid JSON, shape and sample valuesready-for-review; no payment or export is authorised
Parse JSON, validate shape and validate business rules before a separate reviewed application action
Three checks in the downloadable example. Passing them creates a reviewable draft. It neither proves the source document was read correctly nor grants permission to pay or export.Open full-size diagram (SVG) ↗

Run the offline example without a model account

Save the reference file and run it with Node.js 22 or later. It uses built-in modules, performs no network requests and needs no API key. The file exports the small invoice schema and an explicit validator for that schema; it is not a general-purpose JSON Schema implementation.

node structured-output-reference.mjs --demo

Expected outcomes:
valid: ready-for-review
malformed: parse-review
wrongCurrency: business-review
wrongTotal: business-review
externalWrites: 0
Download the offline output-validation example (.mjs) ↓

Synthetic invoice cases, an explicit shape validator and business checks. No model calls, uploads or payments.

The local tests cover malformed JSON, unexpected fields, wrong types, an incorrect invoice identity, currency mismatch, arithmetic, negative values, the review limit and safe-integer boundaries. These are deterministic application checks. They do not measure extraction accuracy or model refusal rates, because no model is called.

Handle refusals and incomplete responses before reading the draft

When connecting a model, first inspect the response envelope and its documented completion outcome. A refusal, interrupted response or tool call is not interchangeable with a completed invoice draft. Route it explicitly instead of parsing an empty string, inventing defaults or treating a missing amount as zero. The downloadable file starts at the candidate text boundary; provider-specific envelope handling belongs in your adapter.

Choose bounded retries according to the error. Correcting an unsupported schema is different from retrying a temporary provider failure. Regenerating extraction text is also different from repeating an external write. Keep the approved draft revision and the outcome of the later write separate so a provider retry cannot silently execute a second action.

A schema does not establish source accuracy or record access

The sample can reject a wrong total, but it cannot tell whether the invoice image really said 10000. Preserve field evidence and evaluate extraction on representative labelled documents. Likewise, a model-supplied customer or invoice identifier does not grant access to that record. Load the expected record through the authenticated user's permissions and compare against server-side context.

Do not let a syntactically valid output carry its own approval. A field such as approved: true is another proposed value unless a trusted application decision establishes it. Review, destination checks and the external operation's result remain their own boundaries.

Make provider changes a small regression check

  1. Keep the provider/model/version, prompt, supported schema and output adapter recorded together.
  2. Run representative good inputs and known failures; keep extraction-quality measures separate from deterministic rule checks.
  3. Include refusals, empty or incomplete responses and values that fit the schema but refer to the wrong business record.
  4. Recheck schema support before switching providers. A keyword present in your local schema is not proof that every server enforces it.
  5. Release external writes only through the application's existing permission and review process.
Built from the work.WeMAIde designs and ships AI software, apps, Telegram products and automation systems.

FROM THINKING TO PRODUCT

Need something like this built?

Tell us what needs to work. We will come back with the clearest next step.

Discuss it in Telegram ↗