Fix model wrapping JSON in ```json fences so JSON.parse throws
The problem
I asked for JSON and got it wrapped in a code fence, so the parser died:
SyntaxError: Unexpected token '`', "`json
{"a"" is not valid JSON
The prompt said "Return ONLY valid JSON, no markdown." It worked ~19 times out of 20, and failed the one unattended run.
What didn't work
- Stronger prompt wording ("ABSOLUTELY NO code fences") — improves the rate, never reaches 100%, and you still need a parser that survives when it fails.
response_format: { type: "json_object" }alone — not supported on every endpoint/model, and some still emit prose before or after the object.- Regex-replacing the fence markers with empty strings — leaves leading prose like "Sure! Here is the JSON:" in the buffer.
The fix
Force structured output where the endpoint supports it, and keep a tolerant extractor as the safety net:
function extractJson<T>(text: string): T {
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
let candidate = (fenced ? fenced[1] : text).trim();
const start = candidate.search(/[[{]/); // skip any prose prefix
const end = Math.max(candidate.lastIndexOf('}'), candidate.lastIndexOf(']'));
if (start === -1 || end === -1) {
throw new Error(`no JSON found in: ${text.slice(0, 200)}`);
}
return JSON.parse(candidate.slice(start, end + 1)) as T;
}
const resp = await client.chat.completions.create({
model: 'gpt-4.1',
messages: [{ role: 'user', content: 'List 3 colors as a JSON object {colors: [{name, hex}]}' }],
response_format: { type: 'json_object' },
});
const { colors } = extractJson<{ colors: { name: string; hex: string }[] }>(
resp.choices[0].message.content!
);
On endpoints that support it, response_format: { type: 'json_schema', json_schema: {..., strict: true} } is even better — the output is then schema-valid by construction.
Why it works
The extractor tolerates fences and prose around the JSON by slicing from the first bracket to the last matching one, and response_format removes the failure mode at the source on endpoints that support it — belt and braces for pipelines that run unattended.