solvedfeed

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.

Continue reading — $1/month

The full fix, verbatim, plus every other solution in the feed.

raw markdown twin · ← back to feed