先聊聊 JSON:它到底是什么
JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种基于文本的轻量级数据交换格式,2006 年成为 RFC 4627 标准,如今的最新规范是 RFC 8259,二十多年过去依然是互联网上最通用的数据格式。它成为事实标准,原因有三点:
- 与 JavaScript 天然亲近:语法脱胎于 JS 对象字面量,前端拿到就能直接用,学习成本几乎为零。
- 语言支持极广:几乎所有主流语言都内置了 JSON 解析与序列化能力。
- 人类可读:相比 XML 的冗长标签,JSON 紧凑清晰,机器与人看着都舒服。
理解了它的定位,再看下面的错误,就会明白标准为什么定得这么严。
1. 尾逗号
在对象或数组的最后一个元素后面多打一个逗号,是 JSON 报错的头号杀手:
{ "name": "微喵", "age": 18, }
正确写法是去掉最后的逗号:
{ "name": "微喵", "age": 18 }
为什么错?规范规定对象的最后一个键值对、数组的最后一个元素后面不能有逗号,这个多出来的逗号叫尾逗号(trailing comma)。解析器读到逗号后会期待下一个值,等来的却是右花括号或右方括号,于是直接抛错。
后果:这是手工维护 JSON 时最常见的翻车点,尤其从 JS 代码里复制对象字面量时。JavaScript 从 ES5 起就允许尾逗号,代码里写惯了,粘贴到 JSON 里就出问题。
2. 用单引号或写注释
JSON 规定:字符串(包括键名)必须用双引号包裹,且不支持注释。下面这种写法全是错的:
{ 'name': '微喵' } // 单引号加注释,直接解析失败
正确写法:
{ "name": "微喵" }
为什么错?JSON 是给机器交换数据用的,不是给人写配置用的,刻意只保留最小语法集:没有注释、单引号、多行字符串。砍掉这些“便利功能”,是为了让解析器实现简单、行为统一。
后果:单引号在 Python 里太自然,顺手就写;注释是从 YAML 或 JS 迁移来的习惯。两种情况都会让解析失败。想写注释,请改用 JSON5 或 YAML,后面有对比。
3. undefined / NaN / Infinity
这三个是 JavaScript 里的特殊值,但不是合法的 JSON 值。JSON 只认六种类型:字符串、数字、布尔、null、对象、数组。
JSON.stringify({ a: undefined, b: NaN, c: Infinity })
上面这行代码的结果是 {"b":null,"c":null}:NaN、Infinity 变 null,undefined 字段被直接吞掉。
为什么错?JSON 的数字必须是有限值,NaN、Infinity 无法表达;undefined 没有对应类型,序列化时只能丢弃(数组里则变 null)。
后果:字段静默丢失,接收方完全不知道它存在过。建议序列化前清洗:undefined 转 null 或删掉,NaN、Infinity 转字符串或 null。
4. 字符串没转义
JSON 字符串里,双引号、反斜杠和换行等控制字符必须转义,否则字符串会提前“断掉”:
{ "path": "C:\new\file" }
正确写法:
{ "path": "C:\\new\\file" }
为什么错?反斜杠在 JSON 里是转义符,\n 会被解析成换行,\t 变成制表符;字符串里出现真正换行是违法的,双引号也可能被误认为字符串结束,所以反斜杠要写成 \\。
后果:轻则解析失败,重则数据被悄悄改写。比如把 C:\temp 存进 JSON,\t 变成制表符,路径就变了。Windows 路径、正则表达式都是转义事故的高发区。
小技巧:别手写转义,让 JSON.stringify 自动处理;工具页的 JSON 格式化工具 也会帮你标出非法转义。
5. 顶层不是对象或数组
很多人把裸字符串或数字当 JSON 用。严格说,JSON 文本的顶层应该是对象或数组:
"hello"
123
true
正确写法:
["hello"]
{"value": 123}
为什么错?早期规范(RFC 4627)要求顶层必须是对象或数组,直到 2017 年 RFC 8259 才放宽为“任意合法 JSON 值”,但很多老工具至今仍只认对象或数组。
后果:同一段 JSON,新解析器接受、老解析器报错,跨语言跨版本容易踩坑。最稳妥的做法是 API 返回值统一包一层对象,如 {"code": 0, "data": ...}。
还有这些坑也别忽视
前面五个是高频错误,下面这几个更隐蔽——不报错,但结果不对。
重复键
{ "name": "微喵", "name": "小花" }
这段 JSON 是合法的,解析不会报错。规范只说“键名应该唯一”,没规定遇到重复键怎么办,于是行为各不相同:Python 的 json 模块和 JS 都是后值覆盖前值,部分 Java 库直接抛异常。
后果:数据静默错乱,合并来源时你根本不知道最后生效的是哪个值。建议序列化前检查键名是否重复。
大整数精度丢失
JS 的数字是 IEEE 754 双精度浮点数,能精确表示的最大整数是 2 的 53 次方减 1,约 9007199254740991,超过就丢精度:
{ "id": 9007199254740993 }
// 在 JS 里解析后变成 9007199254740992
后果:雪花 ID、数据库自增主键、订单号动辄十几位,前端拿到后末尾几位悄悄变成 0。正确做法是把超长整数写成字符串:"id": "9007199254740993"。
数字格式的坑:.5 与 01
JSON 的数字语法比大多数编程语言更严格:不允许前导零,小数点前后必须有数字:
{ "n": 01 } // 前导零,非法
{ "n": .5 } // 小数点前没有数字,非法
{ "n": 1. } // 小数点后没有数字,非法
正确写法:1、0.5、1.0。
后果:01 在 Python 老版本里是八进制,在 JSON 里直接报错;.5 在 JS 里合法,在 JSON 里非法。同一段数字,语境不同规则就不同。
键名不加引号
{ name: "微喵" }
这是 JS 对象字面量,不是 JSON。JSON 的键名必须是带双引号的字符串,很多人把 console.log 打印出来的对象直接当 JSON 复制走,就翻车了。
后果:curl 传请求体、加载配置文件、跨语言传输,所有要求严格 JSON 的场合全部失败。写 JSON 时,键名一律加双引号。
各语言解析差异
同一段“看起来是 JSON”的文本,不同语言、不同解析器的处理可能完全不同:
- 尾逗号:JSON.parse、json.loads 都拒绝;JS 对象字面量等宽容解析器接受。
- 单引号:JSON.parse、json.loads 拒绝;Python 的 eval 能解析,YAML 也能。
- 顶层裸值:现代 JSON.parse 接受,IE8 时代的旧浏览器报错;json.loads 接受。
- 注释:所有标准 JSON 解析器都拒绝;JSON5、YAML 及“宽容模式”解析器会接受。
- 大整数:Python 支持任意精度,JS 丢精度——同一份 JSON 能解析出不同值。
这就是“本地没问题、线上报错”的原因:本地宽容,线上严格。跨语言传输时按最严格的标准写最省事。
JSON vs JSON5 vs YAML
| 特性 | JSON | JSON5 | YAML |
|---|---|---|---|
| 注释 | 不支持 | 支持 | 支持 |
| 尾逗号 | 不支持 | 支持 | 不适用 |
| 键名引号 | 必须双引号 | 可省略或单引号 | 通常可省略 |
| 字符串引号 | 必须双引号 | 单双引号均可 | 单双引号均可 |
| 顶层裸值 | 2017 年后允许 | 允许 | 允许 |
| 适合场景 | 跨语言数据交换 | 配置文件 | 配置文件、CI |
怎么选?机器间交换数据用标准 JSON,兼容性最好;手写配置用 JSON5 或 YAML,能写注释体验更好。但它们的解析器生态远不如 JSON 广泛,别把 JSON5 当 JSON 发给后端。
JSON 报错了?五步定位法
遇到"JSON 解析失败",别对着屏幕干瞪眼,按下面五步走,绝大多数错误几分钟内就能定位:
- 看报错信息给的行号列号:绝大多数解析器会直接告诉你在第几行第几列出错,先跳到那个位置看;
- 查引号是否配对:字符串没闭合、内部引号没转义,是最常见的"行号报错但看着没问题"的原因;
- 查括号和逗号:花括号、方括号是否一一配对,结尾有没有多余的逗号;
- 查值是否合法:有没有裸的 undefined、NaN、Infinity、十六进制数或 01 这种前导零;
- 交给工具验证:把 JSON 贴进 JSON 格式化工具 点"验证",工具会高亮错误位置并给出原因,比自己肉眼扫快得多。
把这五步养成习惯,JSON 报错就不再是拦路虎。配合本文前面讲到的那些坑,基本能覆盖日常开发中遇到的所有情况。
常见问题
Q:JSON 里能写注释吗?
A:标准 JSON 不能。想写注释就用 JSON5 或 YAML,或把说明放进约定字段,如 "_comment": "..."。
Q:为什么 JSON.stringify 输出里没有 undefined 字段?
A:这是设计行为。JSON 没有 undefined 类型,对象里的 undefined 字段会被丢弃,数组里的会变成 null。
Q:JSON 和 JavaScript 对象有什么区别?
A:JSON 是纯文本格式,JS 对象是内存里的数据结构。对象可以有方法、undefined、尾逗号,JSON 统统不行。
Q:如何快速定位 JSON 错误?
A:把 JSON 贴进 JSON 格式化工具 点验证,会显示具体行列和错误原因;或在浏览器控制台跑 JSON.parse 看报错。
行动小结
- 写前过一遍清单:键名加双引号、不加尾逗号、字符串正确转义、顶层用对象。
- 序列化交给 JSON.stringify 这类库函数,别手拼字符串。
- 大整数转成字符串传输;NaN、Infinity、undefined 在序列化前先清洗。
- 跨语言场景按最严格的标准写,兼容性最好。
- 出错时用工具定位,别肉眼硬看:把 JSON 丢进 JSON 格式化工具,几秒钟找到问题位置。
JSON 之所以“烦人”,是因为它只保留了最小必要语法。理解了它的取舍,你就再也不会被它坑了。