JSON 转义是把字符串表示为 JSON 文档一部分的语法操作。它不是加密、URL 编码、Base64、HTML 转义、SQL 引号处理,也不是通用注入防御。在大多数应用中,最安全的做法是把值交给标准序列化器,并让正确边界上的解析器消费结果。
核心要点
- JSON 字符串中的引号、反斜杠以及 U+0000 到 U+001F 的所有控制字符必须使用合法转义表示。
\/可以使用但不是必须;Unicode 字符可以直接出现,也可以使用\uXXXX,具体取决于解析器和传输配置。JSON.stringify("text")返回包含外层引号的完整 JSON 字符串字面量,不只是没有引号的“转义片段”。- 嵌套 JSON 通常应保持嵌套对象,只有外层协议明确要求字符串时才把它 stringify。
- 使用标准序列化和解析库;手动替换、
eval和临时“反转义”逻辑都容易出错。 - 转义只保证语法表示,不能完成授权、HTML 清洗、SQL 注入防护、Schema 校验,也不能让不可信内容变得可执行。
JSON 字符串语法
JSON 字符串由 " 包围,内部不能直接包含未转义的 " 或 \,U+0000–U+001F 控制字符也不能直接出现。常见短转义如下:
| 字符或值 | JSON 表示 | 要求 |
|---|---|---|
| 引号 | \" |
字符串内部必须转义 |
| 反斜杠 | \\ |
字符串内部必须转义 |
| 退格 | \b |
使用转义表示 |
| 换页 | \f |
使用转义表示 |
| 换行 | \n |
使用转义表示 |
| 回车 | \r |
使用转义表示 |
| 水平制表符 | \t |
使用转义表示 |
| 其他 U+0000–U+001F | \u00XX |
必须转义 |
正斜杠 / |
\/ |
可选,直接写 / 也合法 |
JSON 语法不要求所有非 ASCII 字符都写成转义。\uXXXX 表示一个 UTF-16 代码单元;基本多文种平面之外的字符可能以代理对表示,例如 \uD83D\uDE00。解析器应按自身 API 验证 Unicode 行为,并拒绝不合法序列。
转义不等于编码
| 操作 | 目的 | 示例 |
|---|---|---|
| JSON 转义 | 在 JSON 字符串中表示特殊语法字符 | " → \" |
| JSON 序列化 | 把值转换为完整 JSON 文档/字面量 | 对象 → {"id":1} |
| URL 编码 | 表示 URL 组件中的数据 | 空格 → %20 |
| Base64 | 把字节表示为 ASCII 文本 | 字节 → SGVsbG8= |
| HTML 转义 | 在 HTML 上下文中安全表示文本 | < → < |
| SQL 参数化 | 把值绑定到数据库语句 | 驱动参数,不是字符串拼接 |
执行一种操作不会自动执行其他操作。应根据下一个解析器或解释器所在的上下文选择正确措施。
使用标准库序列化值
const value = {
message: 'He said "hello".',
lines: "first\nsecond",
unicode: "世界 😀",
};
const documentText = JSON.stringify(value);
console.log(documentText);
const roundTrip = JSON.parse(documentText);
JSON.stringify("hello") 的结果包含外层引号。如果协议要求完整 JSON 文档,应传递完整结果;除非有明确契约,不要为了得到所谓“无引号转义片段”而截掉引号。
import json
value = {"message": 'He said "hello".', "lines": "first\nsecond"}
document_text = json.dumps(value, ensure_ascii=False)
round_trip = json.loads(document_text)
Python 的 ensure_ascii=False 让非 ASCII 字符更易读;只要传输配置正确使用 UTF-8,生成的 JSON 仍然有效。ensure_ascii=True 是表示选择,不是安全增强。
嵌套 JSON:对象还是字符串?
接收方理解 JSON 时,优先保持嵌套对象:
const structured = {
payload: { name: "Ada", enabled: true },
};
只有外层协议明确把 payload 定义为文本时,才使用 JSON 字符串:
const embedded = {
payload: JSON.stringify({ name: "Ada", enabled: true }),
};
const outerText = JSON.stringify(embedded);
const parsedOuter = JSON.parse(outerText);
const innerValue = JSON.parse(parsedOuter.payload);
每增加一层字符串就增加一次序列化边界和更多反斜杠。不要为了“更安全”反复转义,应定义层数,并在每个边界只解析一次。
反转义与校验
不要通过替换 \"、\\ 来处理任意文本。合法的 JSON 字符串字面量应交给 JSON 解析器:
const literal = '"Hello \\"world\\"\\n"';
const value = JSON.parse(literal);
try {
JSON.parse(userSuppliedText);
} catch (error) {
// 拒绝格式错误的 JSON,不要猜测如何修复。
}
如果 API 提供的是没有外层引号的转义片段,只有在契约保证它是合法 JSON 字符串内容时,才可以构造字面量并解析,同时执行大小限制。绝不要用 eval 或 Function 解释它。
解析只校验语法,不校验业务含义。解析后仍需执行 Schema、类型、大小、深度、授权和业务策略校验。包含 HTML、SQL、Shell 命令、Prompt 或 URL 的字符串,在 JSON 解析后仍是不可信数据。
处理一次性测试字符串时,可以用 JSON 转义工具查看一层转义或反转义结果;它是诊断辅助,不能代替应用边界上的序列化器。JSON 值模型与语法背景参见 JSON 定义。
多语言边界
Go
使用 encoding/json 并检查错误。除非明确知道值是 JSON 字符串且契约要求字符串内容,否则不要切割序列化结果来删除引号。
package main
import (
"encoding/json"
"fmt"
)
func main() {
value := map[string]string{"message": "He said \"hello\""}
data, err := json.Marshal(value)
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
Java
统一使用一个 JSON 库,例如 Jackson,并区分 JSON 树/值和序列化后的文本。在需要时明确配置未知字段、重复字段、数字和 Unicode 策略。
ObjectMapper mapper = new ObjectMapper();
String text = mapper.writeValueAsString(Map.of("message", "He said \"hello\""));
JsonNode value = mapper.readTree(text);
导入和依赖版本应属于构建配置;不要在同一个示例中混用互不相同的 JSON 对象模型而不提供适配边界。
上下文相关的安全边界
HTTP API
只序列化一次请求对象,并使用正确的 Content-Type 发送。不要把用户输入拼接到 JSON 源文本中。服务端仍需认证、授权、Schema 校验、资源限制和未知字段处理。
数据库
通过数据库驱动的 JSON 参数或预编译语句存储 JSON。JSON 转义不能保护字符串拼接出来的 SQL。查询时使用数据库 JSON 函数,并校验留存、访问和日志策略。
HTML 与 JavaScript
JSON 转义不是 HTML 转义。将 JSON 嵌入 <script> 或 HTML 属性需要上下文感知的安全序列化策略、CSP 和正确的定界符处理。不要通过字符串拼接把不可信值放进可执行 JavaScript。
URL 与请求头
JSON 文本放入 URL 查询、路径或 HTTP 请求头时,还要遵守对应上下文规则。必要时序列化后再对组件进行 URL 编码,并限制长度和字符范围。
日志
JSON.stringify 可以生成结构化日志,但不会删除密钥或个人信息。记录前应按字段脱敏,并限制序列化的深度和数据量。
常见错误
手动替换
只替换引号会漏掉反斜杠和控制字符,替换顺序还可能造成双重转义。使用标准序列化器。
双重转义
已经序列化的 JSON 字符串是数据,除非第二层协议确实要求,否则不要再次序列化。明确变量当前是原生值、JSON 文档还是 JSON 字符串字面量。
把 Unicode 转义当成清洗
在特定 JavaScript 嵌入策略中,把 < 转为 \u003C 可能有帮助,但它不能校验 Schema、删除危险 URL 或完成授权。应选择针对具体上下文的防御。
接受非标准值
标准 JSON 不包含 NaN、Infinity、undefined、注释或尾逗号。库可能提供扩展;只有协议明确允许时才启用,并记录互操作影响。
验证清单
- 确认下一个解析器或解释器,并使用它的编码规则。
- 使用标准库序列化值,不手写 JSON。
- 尽可能保持嵌套结构,只有协议要求时才增加字符串层。
- 测试引号、反斜杠、每个控制字符、Unicode、代理对、空字符串、
null、大值和错误输入。 - 解析输出,并按目标数字和 Unicode 策略比较往返值。
- 限制字节、深度、成员、数组和嵌套层数。
- 执行 Schema、授权、脱敏和上下文相关的输出编码。
- 记录解析器/库版本,确保行为可复现。
常见问题
所有非 ASCII 字符都需要 \uXXXX 吗?
不需要。只要传输编码支持,JSON 可以直接包含 Unicode 字符。转义可以服务于只允许 ASCII 的传输兼容性,但不会增加机密性或完整性。
/ 必须写成 \/ 吗?
不必须。/ 和 \/ 都是合法 JSON 字符串表示。某些历史嵌入策略使用 \/,但不要把它当成通用安全保证。
为什么每层嵌套会多出反斜杠?
每层 JSON 字符串都要转义上一层表示引号和转义的反斜杠。应避免不必要的层,并逐层解析。
JSON 转义能防止注入吗?
正确序列化可以防止值破坏 JSON 语法,但不能防止 SQL、HTML、Shell、URL、Prompt 或业务逻辑注入。必须分别校验和授权。
可以通过替换转义序列修复错误 JSON 吗?
通常不能安全修复。应拒绝错误输入并返回精确解析错误;如果确实需要宽松解析器,应明确规定其数据丢失行为。静默修复可能改变用户数据。
一手来源
- RFC 8259:JSON 数据交换语法
- ECMA-404:JSON 数据交换标准
- MDN:
JSON.stringify() - MDN:
JSON.parse() - OWASP:XSS Prevention Cheat Sheet
- OWASP:SQL Injection Prevention Cheat Sheet
总结
正确的 JSON 转义,就是在一个明确边界上按 JSON 语法表示一个值。应使用标准序列化,尽量保留结构,避免不必要的嵌套,通过解析而不是替换来“反转义”,并执行下一个上下文要求的防御。语法正确是必要条件,但只是安全数据处理的一部分。