Developers

JSON Schema Structured Outputs for Document Data Extraction Work

DC
DataConvertPro
~9 min read

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

Filed underDevelopers

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.