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 的資料庫 trigger)會輸出包含未轉義換行或 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 時