TekFinch

Getting an LLM to Output JSON You Can Actually Trust

Tell a model to 'return JSON' and what you often get back is JSON-shaped, not JSON-valid. Here's a practical look at what actually gets you reliable, parseable structured output from an LLM.

TekFinch TeamJune 30, 2026 6 min read
Share:
Getting an LLM to Output JSON You Can Actually Trust

Key Takeaways

  • Just asking for "JSON" in a plain-text prompt tends to produce something close to valid but not quite there - extra commentary, a trailing comma, field names that drift.
  • A dedicated structured-output or JSON mode, when the provider offers one, constrains what the model can generate to match a schema and beats plain instructions by a wide margin.
  • Whatever method you use, parse and validate the response defensively in your own code - even a solid structured-output mode still deserves this for anything feeding another system.

About this app

Ask an LLM to "return the answer as JSON" and get back JSON wrapped in a stray sentence, tagged with a trailing comma, or using a field name that's close but not quite what you asked for - if that's happened to you, you've run into one of the most common headaches in working with these models. The reassuring part is that it's a well-understood problem with well-understood solutions; it's mostly a matter of knowing which lever actually works, and why plain instructions alone tend to come up short.

Why "just tell it to output JSON" falls apart

A plain-text instruction to "return JSON" is a request, not something the model is actually forced to honor. It's still generating text one token at a time, and nothing stops it from tacking on a conversational lead-in ("Sure, here's the JSON you asked for:"), wrapping the whole thing in markdown code fences, leaving in a trailing comma, or naming a field almost-but-not-quite the way you specified. Any single one of those small slips is enough to break a naive `JSON.parse()` call further down the pipeline. And the longer or more elaborate the output you're asking for, the more chances there are for one of these to sneak in.

  • A conversational wrapper: a line or two of preamble or sign-off tacked on before or after the actual JSON.
  • Markdown fencing: the payload shows up wrapped in ``` code fences rather than as raw JSON.
  • Trailing commas or comments: JSON that looks fine to the eye but a strict parser still rejects.
  • Field names that drift: a key called `full_name` in one response and just `name` in the next, from the identical prompt.
  • Mismatched types: a number that comes back as a quoted string, or a single object where you expected an array.

Reach for a dedicated structured-output mode where one's available

A growing number of providers now offer a purpose-built structured-output or JSON mode rather than making you rely on plain instructions. You hand it a schema - field names, types, which ones are required - and generation itself gets constrained to fit that shape, instead of the model merely being asked nicely to follow along. That's a real reliability jump over instructions alone, because it limits what the model is able to output at the token level rather than just what it's told to go for. Wherever your provider and model support it, make it the default for anything you're going to parse in code, not something you reach for only occasionally.

It's worth reading the fine print on whatever mode you're using, since "structured output" doesn't mean the same thing everywhere. Some providers guarantee the result matches your schema exactly; others only guarantee syntactically valid JSON without full schema conformance. Check the documentation for your specific mode rather than assuming you're getting the strongest possible guarantee by default.

Build the schema to guide the model, not just to describe your data

Even when there's no dedicated structured-output feature to lean on, showing a concrete example of the exact shape you want - field names, nesting, types and all - alongside your instructions noticeably improves consistency compared to describing it in prose alone. A worked example hands the model a pattern to copy rather than a spec to interpret, and interpretation is where things go sideways.

Keep the schema as flat and simple as the task will genuinely allow. Deep nesting, optional fields tangled in conditional logic, and sprawling enums all invite more errors than a plain, flat structure would. In practice, reshaping valid simple output in your own code is almost always easier than getting a model to nail a complicated nested structure every single time. When a field can be computed or rearranged afterward, it's usually smarter to ask the model for something simple and handle that transformation yourself.

ApproachReliabilityWhen to use it
Plain-text instruction onlyLow - small deviations show up oftenQuick prototypes, low-stakes output
Instruction plus a worked exampleModerate - cuts down on field driftWhen no structured-output mode exists
Dedicated structured-output/JSON modeHigh - generation itself is constrainedAnything feeding another system
Structured mode plus code-side validationThe strongest reliability in practiceProduction systems, automated pipelines

Validate defensively no matter what method you pick

Regardless of which approach you take, treat the model's output as untrusted input that needs checking before it reaches any downstream system. Parse it carefully, confirm required fields are present and correctly typed, and handle any failure with a retry or fallback rather than assuming things went fine. That holds even with a dedicated structured-output feature in play, since edge cases, unusual input, and changes on the provider's end can still produce something you didn't expect.

  • Parse it first: attempt to parse the raw response, and catch and log any parse failure instead of letting it crash the caller.
  • Check it against the schema: confirm required fields are actually there and typed correctly before you use any of them.
  • Fail loudly, never silently: a missing or malformed field should trigger a retry or a visible error, never a quietly wrong default.
  • Retry with a correction: on failure, a follow-up call that includes the bad output and points out what was wrong often self-corrects.
  • Keep the raw response around: hang onto the unparsed output during development so a failure is debuggable rather than a mystery.

How it all fits together

No single one of these techniques is a silver bullet, but stacked together they cover almost all the ground: a structured-output mode (or a solid worked example when that's not on the table) does the heavy lifting of getting the shape right from the start, a simple schema shrinks the room for error, and defensive validation in your own code mops up whatever still gets through. Treat that combination as the baseline for anything feeding an automated system, and save the loose, instruction-only approach for cases where a person is reading the output and can shrug off the occasional glitch.

Frequently Asked Questions

Does a dedicated structured-output feature guarantee valid output every time?

It's a substantial reliability upgrade over plain instructions, but "substantial upgrade" isn't the same as "guaranteed" - keeping code-level validation as a safety net is still smart no matter which method you're using.

What should happen if the model returns output that fails validation?

Have an explicit fallback path ready rather than letting bad output silently break something downstream - that could be an automatic retry with clarified instructions, a safe default value, or a clearly surfaced error, depending on how your system's built.

Signature Newsletter

The Weekly Dose

One email a week: a genuinely useful app, a quick tip, and nothing you didn't ask for. No spam, unsubscribe anytime.

Join readers who get our best ideas first. We respect your inbox.