如何从语言模型稳定拿到 JSON

要求它给 JSON,不等于拿到的就是 JSON。差距主要在结构上。

王思远发布于 2026-02-05阅读约 6 分钟JSON · 结构化输出 · 解析

「请只返回 JSON」是一句祈使句,不是一种保证。模型会照做,然后加上一句「好的,这是你要的结果」——从语法上说,这句客套就已经让整个回复不再是合法 JSON 了。理解这件事之后,取数逻辑的写法就只有一个方向:不要试图让回复变干净,而是从中找出干净的那一段。

回复为什么总是被包起来

模型被训练成「有帮助的助手」,而助手会解释自己在做什么。于是即使你明确要求只输出 JSON,它也会倾向于加一句开场白或一段总结。这不是指令遵循失败,而是两个目标在互相拉扯。

另外两个常见包装来自格式化层:Markdown 代码围栏,以及为了可读性插入的换行与缩进。前者是展示习惯,后者纯粹是排版。

两种失败的解析写法

大多数取数代码从两个方向踩坑。

第一种是「直接解析」:把回复 trim 一下交给 JSON 解析器。模型一旦加了开场白,这条路立刻断掉,而且报的错通常很难看出真正原因。

第二种是「逐字符删开头」:用一个 try/catch 包住解析循环,从左往右删字符直到成功。它能跑通大多数情况,直到某天返回了一段被截断但恰好合法的前缀 —— 于是错误的数据静默通过,比直接失败更难查。

按结构扫描,而不是按格式处理

更稳的做法是忘掉格式,直接看括号结构:

  1. 从左往右找到第一个 { 或 [,那里是候选起点。
  2. 开始计数嵌套深度,遇到对应的闭合符号递减。
  3. 跳过字符串字面量内部的符号 —— 否则文本里的花括号会把深度计数带偏。
  4. 处理转义序列,让 \" 不会提前结束字符串。
  5. 深度回到零时,这段区间就是第一个配平的 JSON 值。

这个算法不关心围栏、不关心前后说了什么,也不在乎缩进。JSON 提取器跑的就是这套扫描。

提取,但不要修复

需要明确划一条线:提取器只负责取,不负责修。少一个逗号就还是少一个逗号,缺一个引号也照样不合法。

这条线的价值在于失败是显式的。一个会「顺手修一下」的工具,最危险的输出不是报错,而是悄悄补出一个你没打算要的字段。取不到就报取不到,调用方才有机会做出正确决定。

继续阅读

结构化输出的可靠性不来自更强的提示词,而来自取数端的容错。把「找到第一段配平的值」这件事写进解析层,比在提示词里反复强调格式更稳。