博客

JSON转义完全指南

转义是每个开发者迟早都会遇到的话题,通常是在某些东西以令人沮丧且不明显的方式出错时。一个看起来正确的JSON字符串被解析器拒绝;一个应该包含换行符的字符串却显示了字面量 \n;一个正确存储的值在经过API往返后变得乱码。这几乎都可以追溯到转义问题。本指南解释转义存在的原因、哪些字符需要转义,以及如何在不同语言和场景中正确处理转义。

为什么需要转义

JSON字符串以双引号字符作为分隔符。这立即带来一个问题:如果字符串本身包含双引号怎么办?如果不转义,解析器会将内部双引号解释为字符串的结束。

// 无效 — 内部引号提前结束了字符串
{ "message": "She said "hello" and left" }
// 有效 — 内部引号已转义
{ "message": "She said \"hello\" and left" }

除了双引号,其他字符也需要出于结构或安全原因进行转义:

  • 控制字符,如换行符、制表符和回车符,会破坏JSON值的单行表示
  • 反斜杠本身是转义字符,因此字面量反斜杠必须转义,以区别于转义序列
  • 非ASCII Unicode字符可以被转义,以确保在可能无法正确处理Unicode的系统中安全传输

JSON转义序列参考

转义序列字符Unicode码点
"双引号U+0022
\反斜杠U+005C
/正斜杠(可选)U+002F
\b退格符U+0008
\f换页符U+000C
\n换行符(line feed)U+000A
\r回车符U+000D
\t水平制表符U+0009
\uXXXXUnicode字符(4位十六进制数字)任意BMP码点

注意,正斜杠(/)是可选转义的——\// 在JSON中都有效。这出于历史原因,与在HTML的 <script> 标签内嵌入JSON有关,在那里 </ 会结束脚本块。

Unicode转义

JSON中任何Unicode字符都可以使用 \uXXXX 转义序列表示,其中 XXXX 是四位十六进制码点。

{
"greeting": "\u0048\u0065\u006C\u006C\u006F",
"copyright": "\u00A9 2024",
"checkmark": "\u2713"
}

解析后,"\u0048\u0065\u006C\u006C\u006F" 变为字符串 "Hello"。当你需要确保JSON文档只包含ASCII字符,以便在可能损坏高字节字符的系统中传输时,Unicode转义非常有用。

超出U+FFFF字符的代理对

基本多文种平面(BMP)之外的字符(码点超过U+FFFF)——如表情符号和生僻汉字——在JSON的 \uXXXX 表示法中需要代理对。

{
"emoji": "\uD83D\uDE00"
}

两个 \u 序列合在一起表示单个字符 😀(U+1F600)。大多数JSON序列化器会自动处理这个问题,但如果你手动构建JSON,需要注意一个高码点字符需要两个 \u 转义序列。

现代JSON解析器也接受字符串中直接嵌入的原始UTF-8编码字符,所以只要文件是UTF-8编码,"emoji": "😀" 就是有效的JSON。

常见转义陷阱

双重转义

双重转义发生在你对已经转义的字符串再次转义,或者在系统的多个层次上应用转义时。典型例子是在JSON字符串字段内存储JSON。

// 外层JSON中有一个值本身是JSON字符串
{
"payload": "{\"key\": \"value\"}"
}

如果接收此值的系统在存储前再次尝试转义,反斜杠就会被转义:

{
"payload": "{\\\"key\\\": \\\"value\\\"}"
}

经过两轮反转义后你能得到原始值,但经过一轮后会得到一个损坏的字符串。解决方法是在恰好一个层次转义,在恰好一个层次反转义。永远不要对已经转义的值再次转义。

混淆JSON转义与URL编码

JSON转义和URL(百分比)编码是完全不同的机制。空格在URL中是 %20,在JSON字符串中是 " "(无需转义)。正斜杠在URL路径段中是 %2F,在JSON中是 /(或可选的 \/)。

在嵌入JSON之前对值进行URL编码会产生双重编码的乱码:

// 错误:在JSON序列化前对值进行URL编码
const value = 'hello world';
const wrong = JSON.stringify(encodeURIComponent(value));
// 结果:"\"hello%20world\"" — %20在JSON中是字面量文本
// 正确:让JSON.stringify处理转义
const correct = JSON.stringify(value);
// 结果:"\"hello world\"" — 空格在JSON字符串中是合法的

未转义的控制字符

JSON要求控制字符(U+0000到U+001F)必须被转义。直接嵌入JSON字符串中的原始换行符或制表符在技术上是无效的,尽管某些解析器会容忍这种情况。

// 无效 — 字符串值中包含原始换行符
{ "note": "第一行
第二行" }
// 有效 — 已转义的换行符
{ "note": "第一行\n第二行" }

各语言的转义处理

JavaScript

JavaScript的 JSON.stringify() 自动处理所有必要的转义。永远不要在JavaScript中手动构建JSON字符串。

const data = {
message: 'She said "hello"',
path: 'C:\\Users\\alice',
multiline: 'line one\nline two',
};
const json = JSON.stringify(data, null, 2);
// 自动生成正确转义的JSON

JSON.parse() 同样自动反转义:

const parsed = JSON.parse('{"path": "C:\\\\Users\\\\alice"}');
console.log(parsed.path); // C:\Users\alice

注意JavaScript字符串字面量本身的额外转义层:"\\\\" 是一个包含两个反斜杠的四字符JavaScript字符串,代表JSON中一个已转义的反斜杠,解析后得到单个反斜杠。

Python

Python的 json 模块工作方式相同——使用 json.dumps()json.loads(),永远不要手动构建JSON。

import json
data = {
"message": 'She said "hello"',
"path": r"C:\Users\alice",
"multiline": "line one\nline two"
}
json_string = json.dumps(data, ensure_ascii=False)
# ensure_ascii=False保留Unicode字符原样,而非
# 将其转义为\uXXXX序列
# 反转义
parsed = json.loads('{"path": "C:\\\\Users\\\\alice"}')
print(parsed["path"]) # C:\Users\alice

ensure_ascii=False 参数值得了解:默认情况下,Python的 json.dumps() 会将所有非ASCII字符转义为 \uXXXX 序列。如果你确定目标系统能处理UTF-8,设置 ensure_ascii=False 会产生更可读的输出。

模板字符串中的转义

在HTML、SQL或Shell脚本中嵌入JSON时,需要同时为JSON和外层上下文进行转义。最安全的方法是使用外层语言的转义机制处理已序列化的JSON:

// 在HTML数据属性中嵌入JSON
const jsonValue = JSON.stringify({ key: 'value with "quotes"' });
const htmlAttribute = `data-config="${jsonValue.replace(/"/g, '&quot;')}"`;

实用转义技巧

  1. 始终使用库序列化器 — 永远不要通过字符串拼接构建JSON。所有主流语言都有标准JSON库,能正确处理转义。

  2. 检查编码层次 — 当一个值看起来被双重转义时,追踪每个序列化和反序列化层次,找出哪里进行了多次转义。

  3. 路径使用原始字符串字面量 — 在Python中,使用原始字符串(r"C:\Users\alice")或正斜杠("C:/Users/alice"),避免Windows路径中意外产生转义序列。

  4. 手动构建后进行验证 — 如果必须手动构建JSON(例如在Shell脚本中),在使用前先用解析器验证结果。

  5. 明确指定编码 — 序列化到文件时,明确指定UTF-8编码,而不是依赖系统默认值,以确保Unicode字符被一致处理。

使用转义/反转义工具

有时你收到一个被序列化了两次的JSON字符串——一个JSON值本身是一个带引号的转义JSON字符串——你需要将其反转义为可读形式。或者你有一个包含特殊字符的原始字符串,需要安全地嵌入到JSON文档中。

转义/反转义工具能即时处理这些转换,无需编写解析代码。

JSONKit的转义工具让你粘贴原始文本并获得正确转义的JSON字符串表示,或者粘贴已转义的JSON字符串并获得未转义的原始内容。所有处理都在浏览器中进行——数据不会发送到任何服务器。

总结

JSON转义由每个标准JSON库自动处理,这是最重要的收获:使用 JSON.stringify / json.dumps / JsonSerializer.Serialize,让库来完成转义。手动构建JSON是大多数转义错误的根源。

当确实出现转义问题时——双重转义、原始控制字符、编码层混淆——系统地追踪每个序列化步骤。本指南中的转义序列参考表涵盖了JSON规范定义的每个序列。