每一个 JavaScript 开发者都见过这一幕:JSON.parse() 抛了一个 SyntaxError,应用就崩了。那段 JSON 来自一个 API、一个配置文件,或者用户剪贴板里的一段东西 —— 反正它就是解析不了。本指南会一步步讲清楚这是为什么、坏 JSON 在实际中长什么样,以及怎么从容地处理它 —— 从一个简单的 try/catch 到自动修复。
1. JSON.parse() 为什么会失败
JSON 标准(RFC 8259)是有意严格的。JSON.parse() 完全照标准实现 —— 任何偏离,无论多小,都会以一个没有局部恢复的 SyntaxError 抛出:
try {
const data = JSON.parse('{ name: "Ada" }'); // 没引号的 key
} catch (err) {
console.error(err.message);
// SyntaxError: Expected property name or '}' in JSON at position 2
}和浏览器解析 HTML(里面有内建的错误恢复模型)不一样,JSON 解析器是一道硬关。严格解析是个特性:它逼着生产方输出干净的数据。但当你不能控制数据源时 —— LLM 输出、手工编辑的配置文件、第三方 API —— 你就需要一个策略来应对这道关「砰」的关上。
2. 坏 JSON 最常见的模式
在伸手去抓修复库之前, 先认清你面对的是什么。现实里几乎所有的坏 JSON 都落在以下某一类里:
用单引号而不是双引号
// ❌ 非法 JSON
{ 'name': 'Ada Lovelace' }
// ✅ 合法
{ "name": "Ada Lovelace" }JavaScript 对象字面量接受单引号;JSON 不接受。开发者直接把一段对象字面量复制到 JSON 环境里,就会反反复复撞到这个错。
尾随逗号
// ❌ 非法 JSON
{
"name": "Ada",
"active": true, // 尾随逗号
}
// ✅ 合法
{
"name": "Ada",
"active": true
}现代 JavaScript 允许数组和对象里有尾随逗号。JSON 不允许。这是手工编辑的 JSON 文件里最常见的单一错误。
没加引号的对象 key
// ❌ 非法 JSON
{ name: "Ada", score: 98 }
// ✅ 合法
{ "name": "Ada", "score": 98 }JavaScript 对象字面量不要求 key 加引号。JSON 要求所有 key 都是双引号字符串,没有例外。
JavaScript 注释
// ❌ 非法 JSON
{
// 这是用户记录
"name": "Ada",
/* 2024 年新增 */
"active": true
}JSON 没有注释语法。注释有时会被加到配置文件里(JSONC —— 带注释的 JSON —— 是一个流行的扩展),但 JSON.parse() 会拒绝它们。
多行字符串
// ❌ 非法 JSON
{
"description": "line one
line two"
}
// ✅ 合法 —— 把换行转义掉
{
"description": "line one\nline two"
}JSON 里的字符串值必须在同一行上。字符串内部不允许出现字面换行;改用 \n 转义序列。
NaN、Infinity 和 undefined
// ❌ 不是合法 JSON
{ "ratio": NaN, "limit": Infinity, "value": undefined }
// ✅ 合法替代
{ "ratio": null, "limit": 1e308, "value": null } 它们是合法的 JavaScript 值,但不属于 JSON 规范。JSON.stringify() 会悄悄把它们转换(NaN 和 Infinity 变成 null;undefined 属性会整个被丢掉),这可能引发一些隐蔽的往返 bug。
Python 字面量
// ❌ Python 风格 —— 非法 JSON
{ "active": True, "deleted": False, "value": None }
// ✅ 合法 JSON
{ "active": true, "deleted": false, "value": null } JSON 一开始就是设计来在多语言之间互通的。Python 那些首字母大写的 True、False 和 None 是跨语言数据 bug 的常见来源。
3. 「修复」和「解析」不是同一件事
把两种根本不同的操作分清楚很重要:
- 严格解析 —— 校验输入完全是正确的 JSON、精确报告任何错误的位置、拒绝一切偏离。当你能控制数据的生产方、想要强制一份契约时用它。
- 修复解析 —— 用一组启发式规则试着从语法上不完美的输入里恢复出一个合法 JSON 值。当数据来自不可信或不精确的源时用它(LLM 输出、用户剪贴板、老服务)。
修复 parser 会做假设。它撞见 True 就假设你想表达 true。它撞见一个尾随逗号就把它去掉。这些假设对上面那些模式几乎总是对的 —— 但它们也可能默默掩盖一份真正畸形的文档,让「修复」改了你本来的意思。
对的工具取决于场景。在一个有 schema 校验的 API 响应流水线里,你想要严格解析,这样坏数据立刻浮出水面。在一个开发者工具里,有人手贴一段从 Slack 信息复制来的 JSON,那修复解析能省时间。