先聊聊 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. }   // 小数点后没有数字,非法

正确写法:10.51.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

特性JSONJSON5YAML
注释不支持支持支持
尾逗号不支持支持不适用
键名引号必须双引号可省略或单引号通常可省略
字符串引号必须双引号单双引号均可单双引号均可
顶层裸值2017 年后允许允许允许
适合场景跨语言数据交换配置文件配置文件、CI

怎么选?机器间交换数据用标准 JSON,兼容性最好;手写配置用 JSON5 或 YAML,能写注释体验更好。但它们的解析器生态远不如 JSON 广泛,别把 JSON5 当 JSON 发给后端。

JSON 报错了?五步定位法

遇到"JSON 解析失败",别对着屏幕干瞪眼,按下面五步走,绝大多数错误几分钟内就能定位:

  1. 看报错信息给的行号列号:绝大多数解析器会直接告诉你在第几行第几列出错,先跳到那个位置看;
  2. 查引号是否配对:字符串没闭合、内部引号没转义,是最常见的"行号报错但看着没问题"的原因;
  3. 查括号和逗号:花括号、方括号是否一一配对,结尾有没有多余的逗号;
  4. 查值是否合法:有没有裸的 undefined、NaN、Infinity、十六进制数或 01 这种前导零;
  5. 交给工具验证:把 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 看报错。

行动小结

  1. 写前过一遍清单:键名加双引号、不加尾逗号、字符串正确转义、顶层用对象。
  2. 序列化交给 JSON.stringify 这类库函数,别手拼字符串。
  3. 大整数转成字符串传输;NaN、Infinity、undefined 在序列化前先清洗。
  4. 跨语言场景按最严格的标准写,兼容性最好。
  5. 出错时用工具定位,别肉眼硬看:把 JSON 丢进 JSON 格式化工具,几秒钟找到问题位置。
JSON 之所以“烦人”,是因为它只保留了最小必要语法。理解了它的取舍,你就再也不会被它坑了。