JSON Schema Structured Outputs for Document Data Extraction Work
You wired up a strict JSON schema, the validator passes on every document, and the pipeline has not thrown an exception in two weeks. Then someone in finance opens the output and finds an invoice total that does not appear anywhere in the source PDF. The parse succeeded. The data is wrong. Nothing in your stack noticed.
Short Answer
Structured outputs constrain the decoder so the model cannot emit a key you did not define or an enum value outside your list. That is a formatting guarantee, not a correctness guarantee. To make wrong answers detectable, make every field nullable so the model can say "not present" instead of inventing something, and require an evidence span (page number, quoted source text, or bounding box) alongside every extracted value so a reviewer or an automated check can verify the value against the document.
What The Schema Guarantees And What It Does Not
| Failure mode | Caught by strict JSON Schema | What actually catches it |
|---|---|---|
| Missing required key | Yes | Constrained decoding |
| Invalid enum value | Yes | Constrained decoding |
| Wrong type (string where number expected) | Yes | Constrained decoding |
| Malformed JSON, truncated braces | Yes | Constrained decoding |
| Value invented for a field absent from the doc | No | Nullable fields plus evidence spans |
| Correct field, wrong number read off the page | No | Evidence span check, cross-field arithmetic |
| Value pulled from the wrong row of a table | No | Row-level evidence, positional checks |
| Line items silently truncated on long documents | No | Count reconciliation, totals check |
| Date parsed as US when the doc is European | No | Raw string capture plus explicit format field |
| Currency, units, or scale misread | No | Raw string capture, business rule validation |
OpenAI's own documentation frames the guarantee narrowly: the model will not omit a required key or hallucinate an invalid enum value. It says nothing about the semantic accuracy of the values, and it explicitly tells you to keep handling refusals and incomplete responses. Every row in the bottom half of that table is your problem, not the decoder's.
Have a messy PDF? Upload 1-3 sample pages and we will tell you if it is clean, OCR-heavy, or needs human QA.
Rule 1: Every Field Required, Every Field Nullable
The instinct is to mark uncertain fields optional. Do the opposite. Mark every field required and give every field a null union in its type.
The reason is mechanical. Under constrained decoding, a required non-nullable string field cannot be skipped. The model reaches that position in the token stream and the grammar permits only a string. If the document has no purchase order number, the model does not get to stop. It produces the most plausible string it can, which is often a number lifted from a nearby field. Boundary's writeup on this describes the same trap: forced field completion turns "I do not know" into a confident fabrication because the schema left no legal way to express absence.
Making the field optional does not fix it either, because an absent key and a key the model forgot look identical downstream. Required plus nullable gives you a third state that is unambiguous:
{
"po_number": { "type": ["string", "null"] },
"invoice_date": { "type": ["string", "null"] },
"total_due": { "type": ["string", "null"] }
}
Then tell the model in the prompt, plainly, that null is the correct answer for anything not printed on the page and that guessing is a worse outcome than a null. Benchmarks for schema-guided extraction score a correct null on a blank field as a win for exactly this reason. A pipeline that returns 40 fields with 6 honest nulls is more useful than one that returns 40 filled fields where 6 are quietly fictional.
Rule 2: Make Every Value Carry Its Evidence
A bare value is unverifiable. A value with a pointer back to the document is checkable by a script, by a reviewer, or by a second model pass. Wrap each extracted field in a small object instead of a scalar:
{
"total_due": {
"value": { "type": ["string", "null"] },
"page": { "type": ["integer", "null"] },
"quote": { "type": ["string", "null"] },
"confidence": { "type": "string", "enum": ["high", "low"] }
}
}
The quote field is the load-bearing one. It should be the verbatim substring from the document text that the value came from. Once you have it, you can run a cheap deterministic check: does that quote actually appear in the extracted text of that page? If it does not, the field is suspect and gets flagged. No second LLM call needed. This catches a specific and common failure where the value is plausible, the schema is satisfied, and the string simply is not in the source.
Commercial platforms have converged on the same idea. Azure's document analyzers return spans and bounding box coordinates, but only for fields whose method is extraction rather than generation. Tensorlake appends page and bounding box citations to each extracted key. Grounding is treated as a capability separate from extraction, because it is.
Set expectations on precision. Public benchmark work on schema-guided extraction finds word-level grounding considerably harder than page-level grounding, and general vision language models often return no grounding metadata at all unless you build it into the schema. Page plus verbatim quote is the pragmatic middle: cheap to produce, cheap to verify, precise enough to route a reviewer to the right part of the page.
Rule 3: Keep Strings As Strings Until You Have Validated Them
Typing total_due as a number feels like good hygiene. In a constrained decoder it is a quiet source of corruption. The grammar admits only digits, so a model that read "1,234.50 EUR" has to decide, mid-token, what subset of that survives. It might emit 1234.5, or 1234, or 123450. You cannot tell afterward which happened.
Capture the raw string exactly as printed, then parse and validate in your own code where failures are visible and loggable. Same for dates: capture "03/04/2026" as a string, capture the format you believe applies in a separate enum field, and resolve the ambiguity in application logic rather than hoping the model guessed the locale.
The principle: constrained decoding removes the model's ability to signal that a value did not fit. Anything you type narrowly loses information silently. Type loosely in the schema, enforce strictly in code.
Rule 4: Give Reasoning Somewhere To Go, Outside The Values
Forcing a model to reason inside JSON string fields makes it spend effort on escaping quotes and newlines instead of on reading the document. If you want a rationale, give it a dedicated field ordered before the values, or run a free-form pass first and a structuring pass second over that output. The two-pass shape costs an extra call and usually buys back accuracy on dense forms and multi-page tables, where the hard part is finding the value rather than formatting it.
For long documents, add reconciliation fields the schema can carry and your code can check: a declared line item count next to the line items array, a subtotal that should sum to the items, a page count the model claims to have processed. Truncation on long lists is a well documented behavior. A count field turns it from a silent loss into a failed assertion.
When A Software Tool Is The Better Fit
If your documents are one stable template, high volume, and machine generated, buy a template-based extraction tool and skip the schema design work entirely. Deterministic zonal extraction on a fixed layout will beat an LLM on both cost and consistency, and it fails loudly instead of plausibly. Schema-guided LLM extraction earns its keep when layouts vary, when the same field lives in different places across vendors, or when the document is scanned and the text layer is unreliable.
DataConvertPro is the third case: we are a managed service, so a person reviews the flagged fields before the file reaches you. The schema design above is what makes that review affordable. Nulls and evidence spans tell our reviewers which 5 percent of cells to look at instead of all of them.
Frequently Asked Questions
Does strict mode stop the model from hallucinating?
It stops one narrow class of hallucination: invented keys and invalid enum values. It does not stop invented values inside valid keys. Providers are direct about this in their documentation, and the practical consequence is that a 100 percent schema validation pass rate tells you nothing about extraction accuracy.
Should I use confidence scores from the model?
Self-reported confidence from an LLM is weak signal on its own, and a fine-grained scale invites false precision. A two-value or three-value enum is more useful, because it forces a decision rather than a number nobody calibrated. Confidence scores produced by a document analysis service, derived from the extraction engine rather than asked for in a prompt, are more trustworthy for routing to human review.
How do I handle fields that fail verification?
Route, do not retry blindly. A field whose quote does not appear in the source text should be set to null and flagged, not silently regenerated. Retrying the same prompt often produces a different fabrication with the same confidence. Send flagged fields to a targeted second pass scoped to one page, or to a human, depending on how much the value is worth.
Do nullable fields hurt recall?
Slightly, and it is a good trade. Permission to return null means some values that were genuinely present come back empty. Those are recoverable through review because they are visible. Fabricated values are not visible, which makes them the more expensive error. Track both rates separately so the trade stays a decision rather than an accident.
Sources: OpenAI Structured Outputs guide, Structured Outputs Create False Confidence, BAML, Structured outputs guide, Logic, Azure AI Content Understanding confidence and grounding, Tensorlake field-level citations, ExtractBench
Ready to Convert Your Documents?
Stop wasting time on manual PDF to Excel conversions. Get a free quote and learn how DataConvertPro can handle your document processing needs with AI-assisted extraction and human verification.