大多数 JSON 校验问题只有一个方向:看起来没问题、实际却解析失败。AI 生成的 JSON 修复指南 讲的就是那个模式清单。本指南是它的镜像——看起来损坏(或至少可疑)、JSON.parse 却毫无报错地接受它们。这些案例之所以重要,是因为手写的校验器和正则守卫会在解析器运行之前就把它们拒掉,而且其中几个还隐藏着静默的数据变化。
1. 顶层标量值
JSON 文档不一定是对象或数组。自 RFC 8259(2017 年)起,JSON 文本可以是任何 JSON 值:
JSON.parse('123'); // 123
JSON.parse('"hello"'); // hello
JSON.parse('true'); // true
JSON.parse('null'); // null 2017 年之前的 RFC 4627 把 JSON 文本限制为对象和数组,这个旧观念至今还留在大量代码里。断言 typeof value === 'object' 的 API 客户端,以及检查是否存在 或 [ ] 包裹的校验器,会拒绝完全合法的标量响应——比如返回 "ok" 的健康检查端点、返回 42 的计数器。
2. 科学计数法
JSON 数字语法允许指数部分,而大多数人低估了它对「数字是什么」的改变程度:
JSON.parse('1e3'); // 1000
JSON.parse('1.5e-7'); // 1.5e-7
JSON.parse('-0'); // -0 指数可以是负数,尾数可以是小数,正负号可有可无。还有一个连仔细的读者都会惊讶的案例:-0 是一个独立的值——Object.is(-0, JSON.parse('-0')) 的结果是 true——但随后 JSON.stringify(-0) 会输出 "0"。解析时它活了下来,但并不是每种表示都会记住它。
3. 大到变成 Infinity 的数字
JSON.parse('1e999');
// Infinity 这是大整数精度指南的指数面镜像:文本完全合法,解析也成功。1e999 只是超过了 Number.MAX_VALUE,所以 JavaScript 必须给出一个结果——并选择了静默的 Infinity。没有异常,没有警告。
Infinity——而 JSON.stringify(Infinity) 会输出 null,在出口处再次损坏数据。无损解析器会保留 token 1e999,把决定权留给你,而不是在解析那一刻就提交给 Infinity。
4. Unicode 转义与转义字符
字符串语法是 JSON 里少数意外宽容的地方。任意四个十六进制位的 \uXXXX 都是合法的——不检查码点是否有效,也不要求代理对成对出现:
JSON.parse('"\u0041"'); // "A"
JSON.parse('"\/"'); // "/"
JSON.parse('"\uD800"'); // 孤立代理项,解析不报错 所以 "A\u0041lice" 和 "Alice" 是同一个字符串——转义写法与直接写法等价;而孤立的 \uD800 是合法的 JSON 文本,解码后代表一个未配对的代理项。做字符白名单的校验器、假定转义只有 \n 或 \t 的工具,都会误判真实载荷。
5. 重复键
JSON.parse('{"a": 1, "a": 2}');
// { a: 2 } 语法允许重复的键名。RFC 8259 说的是键名「SHOULD 唯一」——建议,不是要求。JavaScript 保留最后一个,大多数其他解析器也一样,但生态并不一致:有些库保留第一个,有些直接抛错。同一份文档在不同解析器里的行为可能不同,这让重复键成为生成式配置里一个安静的互操作隐患。
如果你控制生产者,就把唯一性当作真实的要求。如果你只消费载荷,要知道你读到的值取决于解析器的重复键策略,而不只是文档本身。
五个案例一览
| 案例 | 示例 | JSON.parse 结果 | 为什么合法 |
|---|---|---|---|
| 顶层标量 | 123 | 123 | RFC 8259 允许任何 JSON 值作为文档 |
| 科学计数法 | 1e3 | 1000 | 指数部分是数字语法的一部分 |
| 溢出的指数 | 1e999 | Infinity(静默) | 语法合法;数值超出 Number 范围 |
| Unicode 转义 | "A" | "A" | 任意四个十六进制位都是合法转义 |
| 重复键 | {"a": 1, "a": 2} | {"a": 2} | 语法允许重复;唯一性只是 SHOULD |
镜像清单:看起来合法,其实不是
作为对照,反向案例——前导零(01)、裸 NaN、尾逗号、单引号、注释——全部非法。JSON.parse('01') 抛出 “Unexpected number in JSON at position 1”,JSON.parse('NaN') 抛出 "NaN" is not valid JSON。它们看起来无害、失败起来却很硬,这正是 AI 生成 JSON 修复指南 里用真实报错逐一讲解的清单。
JSONDock 如何处理这些案例
JSONDock 使用无损表示解析,所以上面五种案例都能忠实于原始文本:1e999 显示为 1e999,并被标记为不安全数字,而不是静默变成 Infinity;-0 保持 -0;重复键明确按「后者胜出」处理。而镜像清单里的案例,修复标签页会在解析前先规范化语法。检查工具不应该替你决定值的命运。