SyntaxError: Bad control character in string literal in JSON at position N 意思是在某个 JSON 字符串里出现了一个未经转义的原始控制字符 —— 一个 tab、换行、回车,或者 U+0000–U+001F 范围内的任意字符。JSON 规范禁止这种写法。本文讲清楚为什么会这样、它们从哪里来,以及怎么把它们清掉。
我撞上的是哪一种字符串错误?
- Bad control character —— 一个原始 tab/换行/null byte 出现在 字符串内部,没有被转义。
- Bad escaped character —— 一个
\后面跟了 JSON 不允许的东西(比如\x、Windows 路径)。 - Unterminated string —— 用
"开了字符串但一直没关。
什么是控制字符?
Unicode 的前 32 个码位(U+0000 到 U+001F)是控制字符 —— 从电传打字机时代继承下来的、不可见的格式控制码。一些你可能熟悉的:
| 码位 | 名称 | JSON 转义 |
|---|---|---|
U+0000 | Null | |
U+0008 | Backspace | \b |
U+0009 | 水平 tab | \t |
U+000A | Line feed(换行) | \n |
U+000C | Form feed | \f |
U+000D | Carriage return | \r |
U+001B | Escape(ANSI 序列的起点) | |
JSON 规范(RFC 8259 §7)说得很明确:当这个范围里的字符出现在 JSON 字符串内部时,必须被转义。一个原始的换行夹在引号里就是语法错误,不是数据。
为什么原始换行会让 JSON 解析失败
想象一个字符串值里真的有个换行字符:
// 字节级别看到的(这个换行是字面意义上的换行,不是 \n):
{"message":"line one
line two"}
JSON.parse('{"message":"line one\nline two"}')
// SyntaxError: Bad control character in string literal in JSON at position 20parser 看到开头的双引号,读字符往下走,然后撞上一个原始的 0x0A 字节。因为 JSON 字符串必须是两个双引号之间、同一逻辑行上一段不被中断的字符序列,这个裸换行就把字符串意外终止了。
正确的写法是用两个字符的转义序列 \n:
// 正确 —— \n 是转义,不是字面换行
{"message":"line one\nline two"}
JSON.parse('{"message":"line one\nline two"}') // ✓ 可以控制字符是怎么混进 JSON 的
1. 用模板字符串或拼接拼 JSON
const note = `first line
second line`; // 来自模板字符串里真实的换行
const json = `{"note":"${note}"}`; // 原始换行嵌进了字符串里
JSON.parse(json); // SyntaxError: Bad control character…
// ✓ 修法:永远用 JSON.stringify 处理值,绝不手工内插
const json = JSON.stringify({ note }); // 自动把换行转义掉2. 读文件后原样嵌入内容
import fs from 'fs';
const content = fs.readFileSync('notes.txt', 'utf8');
// content 里可能含有 \n、\r\n、\t 等
// ❌ 手工拼字符串 —— 嵌入了原始控制字符
const json = '{"content":"' + content + '"}';
// ✓ JSON.stringify 会处理所有转义
const json = JSON.stringify({ content });3. 来自不做输出转义的系统的 API 响应
有些后端系统(老旧 PHP 脚本、自定义 serializer、手工拼 JSON 的数据库触发器)会输出含有未转义换行或 tab 的字段值。你拿到的就是语法上非法的 JSON,response.json() 就会抛错。
诊断时打印原始 body 长度和前 200 字符。看看本该是字符串值的位置里有没有出现可见的换行。
4. 从终端复制粘贴(ANSI 转义序列)
终端输出里含有 ANSI 颜色码,它们以 Escape 字符(0x1B)开头,后面跟一个 [。如果你把终端输出粘进一个 JSON 字符串,每一次颜色重置(\x1B[0m)都会变成一个控制字符。
// 把终端输出粘到 JSON 字符串里:
{"log":"\u001B[32mOK\u001B[0m request processed"}
// ^^^ 原始 ESC 字节 —— 本该写成 6 个字符的 \u001B 转义序列5. 来自二进制数据的 null 字节
把二进制文件的一部分、一个数据库 BLOB 列、或者一个 C 风格字符串读进 JSON 字段时,可能会嵌入 null 字节(U+0000)—— 这是「数据库到 JSON」流水线里这个错误最常见的来源。