Why LLM JSON breaks
Even with JSON mode, you will occasionally receive output that JSON.parse() rejects — usually from truncation, an older model, or a prompt that did not constrain the format. The failures are remarkably predictable, which means most can be repaired automatically.
The seven common breakages
| Problem | Example | Fix |
|---|---|---|
| Markdown fences | `json {…} ` | Strip leading/trailing fences |
| Preamble text | Here is the JSON: {…} | Slice from first { or [ |
| Trailing comma | {"a": 1,} | Remove comma before }/] |
| Single quotes | {'a': 1} | Convert to double quotes |
| Comments | {"a": 1 // note} | Strip // and /* */ |
| Unterminated string | {"a": "hel | Truncation — re-request or close string |
| Python literals | {"a": True, "b": None} | Map True/False/None to true/false/null |
A defensive parser
function repairJson(raw: string): string {
let s = raw.trim();
// 1. Strip Markdown fences
s = s.replace(/^\`\`\`(?:json)?\n?/i, "").replace(/\n?\`\`\`$/i, "");
// 2. Slice to the first JSON token
const start = s.search(/[{[]/);
if (start > 0) s = s.slice(start);
// 3. Python-style literals -> JSON
s = s.replace(/\bTrue\b/g, "true")
.replace(/\bFalse\b/g, "false")
.replace(/\bNone\b/g, "null");
// 4. Remove trailing commas
s = s.replace(/,(\s*[}\]])/g, "$1");
return s;
}
function safeParse(raw: string) {
try { return JSON.parse(raw); }
catch { return JSON.parse(repairJson(raw)); }
}This handles the majority of real-world cases. For single quotes and comments, the safest approach is a tolerant parser (such as JSON5) rather than fragile regex — quotes inside string values make naive replacement dangerous.
import JSON5 from "json5";
// JSON5 tolerates comments, single quotes, trailing commas, and unquoted keys
const data = JSON5.parse(repairJson(raw));When repair is the wrong answer
Some failures should not be patched over:
- Truncation. An unterminated string means the response was cut off — you are missing data. Increase
max_tokens, or split the task, and re-request. - Wrong shape. If the JSON parses but has the wrong fields, repair will not help. Validate with a schema and re-prompt.
- Hallucinated data. Valid JSON full of invented values is worse than a parse error because it fails silently. Always validate semantics, not just syntax.
Preventing breakage in the first place
- Use the provider's structured-output / tool-use mode.
- Explicitly forbid Markdown: "Respond with raw JSON only."
- Prefill the assistant turn with
{where supported. - Keep outputs small enough to avoid truncation.
- Set temperature low for extraction tasks.
Debugging a stubborn response
When a payload still will not parse, do not squint at it — paste it into a formatter or auto-repair tool. A good tool highlights the exact line and column where parsing failed, so a missing brace 400 characters in becomes obvious instantly.