转义是为了什么
JSON 字符串用双引号界定。所以一旦里面的文本自己需要一个双引号,冲突就来了:解析器会把那个引号当成字符串结束。转义的办法是在前面加个反斜杠 —— \" 表示一个真正的引号字符,不是界定符。
同一招也覆盖其他不能原样出现在字符串里的东西:反斜杠本身,以及 U+0020 以下的控制字符。
| 转义 | 含义 |
|---|---|
\" | 一个双引号,不是字符串结束 |
\\ | 一个真正的反斜杠 |
\n | 换行 |
\r | 回车 |
\t | 制表符 |
\b \f | 退格与换页 |
\/ | 正斜杠。合法,但从不必需 |
\uXXXX | 按码点表示任意字符 |
哪些不需要转义
这点值得单独讲,因为转义过度和转义不足一样常见。非 ASCII 文本完全不需要转义。JSON 本身就是 Unicode,所以带音标的字母、汉字、emoji 原样放在字符串里都是合法的。
- 正斜杠永远不需要。
https://example.com直接写就行。\/是合法的,有些编码器会输出它,这就是日志里的 URL 常常莫名其妙很难看的原因。 - 单引号永远不需要。 它在 JSON 里没有特殊含义,所以
\'不是合法转义,是错误。 - 控制字符永远需要。 字符串里出现真正的制表符或换行是非法 JSON。它们变成
\t和\n;更冷僻的会变成\u0001这种形式。
输入 JSON
{"s":"café 中文 😀"}转义后
"{\"s\":\"café 中文 😀\"}"café、中文 和 emoji 都原样穿过去了 —— 写成 \u00e9 也合法,只是没人读得懂。控制字符
字符串值里的制表符和换行不能直接写,所以会变成两个字符的转义序列。这是转义唯一一处会让长度变化到肉眼可见的地方:多行字符串会明显变长。
输入 JSON
{"s":"a\tb\r\nc"}转义后
"{\"s\":\"a\\tb\\r\\nc\"}"\t 本来就是个转义序列。转一轮之后变成 \\t —— 一个反斜杠加一个 t,这就是外层如何把它保住的。反斜杠为什么会翻倍
这才是那团乱麻真正的来源。某个服务要记录请求体,做法是把整份 JSON 文档塞进另一份 JSON 文档的一个字符串字段里。内层文档的引号现在处于字符串内部,于是每一个都得到一个反斜杠。
原始数据
{"user":{"name":"Ada"},"ok":true}作为 JSON 字符串存起来
"{\"user\":{\"name\":\"Ada\"},\"ok\":true}"两层,以及翻倍
做两次 —— 某个服务记录了一条本身就含着已记录数据的消息 —— 第一轮产生的每个反斜杠现在自己也需要转义。一个引号变成 \\\":一个被转义的反斜杠,跟一个被转义的引号。
所以数量看着像是随机的。其实不是:每一层大致让反斜杠翻一倍,所以 1 个、3 个、7 个反斜杠分别对应一层、两层、三层编码。
一层
"{\"user\":{\"name\":\"Ada\"},\"ok\":true}"两层
"\"{\\\"user\\\":{\\\"name\\\":\\\"Ada\\\"},\\\"ok\\\":true}\""把数据还原出来
去转义就是反向操作,一次一层。把转义过的字符串贴进去,就能拿回格式化好的文档:
- 按一次去掉一层。 双重编码的字符串还原出来仍然是个转义过的字符串,不是文档。再按一次。
- 没东西可去掉时按钮是禁用的。 只有当输入能解析成字符串、而这个字符串本身又能解析成 JSON 时它才亮起 —— 所以误点一下不会把一份正常文档搞坏。
- 贴进转义过的数据时格式化工具会认出来。 它会明确告诉你,而不是默默重新排版,因为「恰好含着 JSON 的字符串」和「JSON 文档」是两回事。
转义过的输入
"{\"user\":{\"name\":\"Ada\"},\"ok\":true}"去掉转义后
{
"user": {
"name": "Ada"
},
"ok": true
}从源头避免
嵌套编码几乎都是意外。如果产生它的代码在你手上,下面这些是修法,按收益从大到小排。
- 日志记结构,别记字符串。 多数日志库的字段可以直接收一个对象。
logger.info({ body: payload })会正确嵌套;JSON.stringify(payload)制造的就是你后面要一层层剥的那一层。 - 存进 JSON 列之前不要先 stringify。 Postgres 的
jsonb之类直接收值。先 stringify 存进去的是个字符串,之后每次查询都要加一次类型转换。 - 永远不要用拼字符串的方式造 JSON。 转义不足就是这么来的 —— 某人姓名里的一个引号提前结束了字符串,整份数据就非法了。用库去序列化。
- 检查消息队列里的内容。 本来就是 JSON 的消息体,进入一个承载文本的队列时不需要再编码一次。
去试试,或者继续读
- JSON 格式化工具栏里有「转义」和「去除转义」,粘贴转义过的内容时会自动认出来。
- JSON 校验与 Schema数据被拒的另一半原因。
- JSON 能写注释吗规范同样不允许的另一件事,以及能怎么办。
- JSON 完全指南语法、类型,以及那些报错背后的规则。