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 上下文中安全表示文本 <&lt;
SQL 参数化 把值绑定到数据库语句 驱动参数,不是字符串拼接

执行一种操作不会自动执行其他操作。应根据下一个解析器或解释器所在的上下文选择正确措施。

使用标准库序列化值

javascript
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 文档,应传递完整结果;除非有明确契约,不要为了得到所谓“无引号转义片段”而截掉引号。

python
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 时,优先保持嵌套对象:

javascript
const structured = {
  payload: { name: "Ada", enabled: true },
};

只有外层协议明确把 payload 定义为文本时,才使用 JSON 字符串:

javascript
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 解析器:

javascript
const literal = '"Hello \\"world\\"\\n"';
const value = JSON.parse(literal);

try {
  JSON.parse(userSuppliedText);
} catch (error) {
  // 拒绝格式错误的 JSON,不要猜测如何修复。
}

如果 API 提供的是没有外层引号的转义片段,只有在契约保证它是合法 JSON 字符串内容时,才可以构造字面量并解析,同时执行大小限制。绝不要用 evalFunction 解释它。

解析只校验语法,不校验业务含义。解析后仍需执行 Schema、类型、大小、深度、授权和业务策略校验。包含 HTML、SQL、Shell 命令、Prompt 或 URL 的字符串,在 JSON 解析后仍是不可信数据。

处理一次性测试字符串时,可以用 JSON 转义工具查看一层转义或反转义结果;它是诊断辅助,不能代替应用边界上的序列化器。JSON 值模型与语法背景参见 JSON 定义

多语言边界

Go

使用 encoding/json 并检查错误。除非明确知道值是 JSON 字符串且契约要求字符串内容,否则不要切割序列化结果来删除引号。

go
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 策略。

java
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 不包含 NaNInfinityundefined、注释或尾逗号。库可能提供扩展;只有协议明确允许时才启用,并记录互操作影响。

验证清单

  1. 确认下一个解析器或解释器,并使用它的编码规则。
  2. 使用标准库序列化值,不手写 JSON。
  3. 尽可能保持嵌套结构,只有协议要求时才增加字符串层。
  4. 测试引号、反斜杠、每个控制字符、Unicode、代理对、空字符串、null、大值和错误输入。
  5. 解析输出,并按目标数字和 Unicode 策略比较往返值。
  6. 限制字节、深度、成员、数组和嵌套层数。
  7. 执行 Schema、授权、脱敏和上下文相关的输出编码。
  8. 记录解析器/库版本,确保行为可复现。

常见问题

所有非 ASCII 字符都需要 \uXXXX 吗?

不需要。只要传输编码支持,JSON 可以直接包含 Unicode 字符。转义可以服务于只允许 ASCII 的传输兼容性,但不会增加机密性或完整性。

/ 必须写成 \/ 吗?

不必须。/\/ 都是合法 JSON 字符串表示。某些历史嵌入策略使用 \/,但不要把它当成通用安全保证。

为什么每层嵌套会多出反斜杠?

每层 JSON 字符串都要转义上一层表示引号和转义的反斜杠。应避免不必要的层,并逐层解析。

JSON 转义能防止注入吗?

正确序列化可以防止值破坏 JSON 语法,但不能防止 SQL、HTML、Shell、URL、Prompt 或业务逻辑注入。必须分别校验和授权。

可以通过替换转义序列修复错误 JSON 吗?

通常不能安全修复。应拒绝错误输入并返回精确解析错误;如果确实需要宽松解析器,应明确规定其数据丢失行为。静默修复可能改变用户数据。

一手来源

总结

正确的 JSON 转义,就是在一个明确边界上按 JSON 语法表示一个值。应使用标准序列化,尽量保留结构,避免不必要的嵌套,通过解析而不是替换来“反转义”,并执行下一个上下文要求的防御。语法正确是必要条件,但只是安全数据处理的一部分。