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 20
parser 看到开头的双引号,读字符往下走,然后撞上一个原始的 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」流水线里这个错误最常见的来源。
怎么修
预防:永远用 JSON.stringify
这是一劳永逸的解。JSON.stringify() 会正确地把每一个控制字符转义掉 —— 你永远不需要手工转义。
// ✓ 不管 userInput 里有什么都安全
const json = JSON.stringify({ message: userInput });
事后修复:在解析前剥掉或转义
如果你拿到的是外部源传来的坏 JSON 而且没法去改生产端,可以在解析前先清洗原始字符串。最安全的做法是把裸控制字符转义掉:
function escapeControlChars(raw) {
return raw.replace(
/[\u0000-\u001F]/g,
(ch) => '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0')
);
}
const fixed = escapeControlChars(rawFromApi);
const data = JSON.parse(fixed);
如果这些控制字符本来就是噪音(ANSI 码、null 字节)而不是有意义的数据,直接剥掉更简单:
// 删掉所有控制字符,但保留 \t、\n、\r(文本里很常见)
const cleaned = raw.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '');
const data = JSON.parse(cleaned);
另外两种现实里的来源
- 位置 0 的 UTF-8 BOM。 以「UTF-8 with BOM」保存的文件开头是
0xEF 0xBB 0xBF。RFC 8259 禁止 JSON 含 BOM,而JSON.parse会以一个 position 0 的错误拒绝它,那个错误经常被误读成控制字符问题。在解析前用text.replace(/^/, '')剥掉,或者在编辑器里把文件保存成「UTF-8」(不带 BOM)。 - VS Code:「Remove Control Characters」。 如果你想手工清一个文件,VS Code 的
Selection → Remove All Occurrences of Find Match配合正则匹配[\x00-\x08\x0b\x0c\x0e-\x1f](保留真正的\t\n\r)能一次把不可见的控制字节都删掉。如果你经常做这事,也有一些扩展提供 「Remove Control Characters」命令。
找出问题位置
错误信息里带一个字节位置。用它去看那里到底是什么:
try {
JSON.parse(raw);
} catch (e) {
const pos = Number(e.message.match(/position (\d+)/)?.[1]);
if (!isNaN(pos)) {
const context = raw.slice(Math.max(0, pos - 20), pos + 20);
console.log('Context around error:', JSON.stringify(context));
// JSON.stringify 会把控制字符重新转义出来,方便你看到它们
// 比如 "...line one\nline two..." —— 那个 \n 原本是真换行
}
}
常见问题
JSON 里的「bad control character」是什么?
一个夹在 JSON 字符串里、未经转义的原始字符,范围在 U+0000–U+001F —— tab、换行、回车、null 字节,或者 ANSI escape。RFC 8259 §7 要求这种字符都必须被转义(比如换行写成 \n)。
怎么修 bad control character 错误?
一劳永逸的解是用 JSON.stringify() 构造 JSON,它会自动把控制字符转义掉。如果你接收的坏 JSON 没法在源头修,那就在解析前用正则把控制字符转义或剥掉(见上面的代码片段)。
为什么原始换行会让 JSON 失败?
JSON 字符串必须是两个双引号之间一段不被中断的字符序列。一个字面意义上的换行字节(0x0A)会过早地结束字符串,所以 parser 报一个控制字符错误。合法写法是两个字符的转义 \n。
怎么从 JSON 里去掉 ANSI 颜色码?
ANSI 序列以 Escape 字节(U+001B)开头。解析前用 raw.replace(/\[[0-9;]*m/g, '') 把它们剥掉,或者一开始就别把原始终端输出粘到 JSON 字符串里。
在浏览器里修复坏 JSON
把你的坏 JSON 贴到 fixjson.org 上的 JSON Fix。宽容 parser 会识别出控制字符违规并报出精确位置。简单情况它还能自动修字符串。
- JSON Fix —— 校验、修复并格式化非法 JSON
- 修复「[object Object] is not valid JSON」 —— JSON 语法错误的完整指南
- 如何修复 JSON.parse 的「Unexpected Token」错误 —— SyntaxError 的每种变体及成因
- Unexpected end of JSON input —— 结构还没闭合就被截断时
- Unexpected token o 解析 —— 当你把对象而不是字符串传给 JSON.parse 时
来源
- RFC 8259 §7 — JSON 字符串语法(禁止未转义的控制字符 U+0000–U+001F)
- MDN —
JSON.parse(发出此错误的解析器) - MDN —
JSON.stringify(预防方案——始终生成正确转义的输出) - Unicode C0 Controls — JSON 转义规则涵盖的 U+0000–U+001F 范围的标准图表
最后审阅:2026 年 6 月。