Repair LLM JSON Output

AI responses often look like JSON but include markdown fences, comments, Python-style literals, or JavaScript object syntax.

Common LLM JSON Failure Modes

LLM responses look like JSON but routinely fail strict RFC 8259 parsing. The same dozen-or-so issues account for almost every broken response:

What the model emits Why JSON rejects it Fix
Markdown code fence (```json … ```) wrapping the JSON The fence and language tag are not JSON syntax Strip the fence before parsing
Prose around the JSON ("Here's the JSON:" … "Hope this helps!") Extra text after the closing brace Extract the first balanced {…} or […]
Single quotes around strings or keys JSON requires double quotes See the fix-single-quotes guide
Unquoted keys ({ name: "Ada" }) JSON requires keys to be quoted strings See the fix-unquoted-keys guide
Trailing commas RFC 8259 forbids them See the fix-trailing-comma guide
Python literals (True, False, None) JSON uses lowercase true / false / null Lowercase + replace None with null
Line / block comments (//, /* */) JSON has no comments Strip them
Smart quotes ("…" curly-quoted) instead of straight " Different Unicode code points; the parser only accepts U+0022 Replace with straight quotes
Truncated output (no closing brace) The response cut off mid-document Buffer until complete, or use a partial-JSON parser

Broken LLM Response

A typical response from a chat model — looks like JSON, isn't:

Here's the JSON you asked for:

```json
{
  // user record
  'name': 'Ada Lovelace',
  active: True,
  notes: None,
  tags: ['dev', 'lovelace',],
}
```

Hope this helps!

Eight separate things would have to go in the bin before JSON.parse accepts it: the leading prose, the fenced wrapper, a comment, single quotes around name, single-quoted string values, an unquoted active key, Python True / None, the trailing comma, and the trailing prose. Either the model has to be constrained at the source, or the response has to be repaired before parsing.

Repaired JSON

{
  "name": "Ada Lovelace",
  "active": true,
  "notes": null,
  "tags": ["dev", "lovelace"]
}

Same data, expressed as strict JSON.

Repair Workflow

  1. Strip the wrapper. Remove leading and trailing prose. If a markdown fence is present, extract the contents between ```json and the closing ```.
  2. Auto-repair the syntax. Paste the extracted text into JSON Fix — it handles fences, single quotes, unquoted keys, trailing commas, Python True / False / None, and line / block comments in one pass, in your browser. In Node, the jsonrepair npm package does the same programmatically with zero dependencies.
  3. Validate strictly. Run the result through the strict JSON validator (or JSON.parse) to confirm RFC 8259 conformance before using the data.
  4. Validate the shape. Auto-repair fixes the grammar, not the schema. Run the parsed object through a schema check (Zod, Ajv, Pydantic, or a hand-rolled validator) to confirm required fields and types are present before acting on it.

Tools and Recipes

The browser tool (JSON Fix) and the jsonrepair npm package implement the same repair rules. For partial / streaming responses, see partial-json on npm — it returns the best valid prefix of an in-progress JSON document so a UI can render fields as they arrive.

// Node — repair, then parse, then validate the shape
import { jsonrepair } from 'jsonrepair';
import { z } from 'zod';

const Schema = z.object({ name: z.string(), active: z.boolean() });

const raw     = await callTheModel();
const repaired = jsonrepair(raw);        // fixes grammar
const parsed   = JSON.parse(repaired);   // confirms strict JSON
const value    = Schema.parse(parsed);   // confirms the shape

Prompting to Reduce Repair Work

The cheapest fix is the one you never need. Constrain the output:

Respond with a single JSON object and nothing else.
No markdown fence, no commentary, no leading or trailing text.

If the model provider supports it, use structured outputs at the API level — they eliminate fences and prose by construction, not by hope:

Function-calling-style schemas (the most constraining option across providers) further reduce hallucinated keys.

Streaming JSON from an LLM

While tokens are streaming, the partial response is invalid by definition — there's no closing brace yet. Two patterns work:

  • Buffer then parse. Accumulate the full response, then JSON.parse once. Simplest and most reliable. Use this when the response is small or the user can wait.
  • Streaming-tolerant parser. Use partial-json, jsonrepair, or clarinet to return the best valid prefix on each chunk, so a UI can render fields as they arrive. Required for token-by-token rendering of long structured outputs.

Either way, the final parse should be strict — streaming-tolerant parsing is for the in-progress display, not for the value you act on.

When to Fail Loudly Instead of Repairing

If the JSON drives a financial transaction, a permission change, or any destructive action, do not silently repair — reject the response and retry, or surface the raw output for a human to review. Repair is appropriate for display, debugging, and recovery; never for inputs that change state without human review. The same logic applies to LLM agents calling tools: an agent that auto-repairs malformed tool-call JSON can take the wrong action on a quietly-corrupted payload.

A reasonable default: repair-and-log in dev, repair-and-alert in staging, strict-parse-or-refuse in production for any action with side effects.

Privacy Note

Repair happens locally in the browser when you use JSON Fix — useful when debugging prompts that include internal examples, real API payloads, or anything you can't paste into a hosted tool. Same applies to jsonrepair running in your Node process: nothing leaves the box.

See also

This guide is part of the LLM-output repair workflow — strip code fences, fix quote style, validate before parsing.

Sources

Last reviewed June 2026.