← 全部文章

JSON vs JavaScript 物件:為什麼不允許單引號

很多開發者把 JS 物件字面量當作 JSON。它們並不相同:單引號、未加引號的鍵、尾隨逗號、undefined、NaN —— 這裡把每處差別配範例講清楚。

你把看起來合法的資料貼到 JSON parser 裡,它立刻丟出 SyntaxError: Unexpected token '''。這段資料來自你的 JavaScript 原始碼,看起來完全合理 —— 但 JSON 與 JavaScript 物件字面量並不是同一種格式,差異比大多數開發者意識到的更重要。

簡短答案:JSON 不支援單引號

在 JSON 中單引號完全非法。每一個字串 —— key 與值都算 —— 都必須使用雙引號。沒有例外。

// ❌ 非法 JSON —— 單引號字串
{ 'name': 'Ada Lovelace', 'active': true }

// ✅ 合法 JSON
{ "name": "Ada Lovelace", "active": true }

你在不同環境會看到的錯誤:

// Chrome / Node.js (V8)
SyntaxError: Unexpected token "'", "{'name':..." is not valid JSON

// Firefox
SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data

// Safari
SyntaxError: JSON Parse error: Single quotes (') are not allowed in JSON

Safari 的錯誤訊息最有用 —— 它直接告訴你問題究竟是什麼。其他幾個只是回報那個意外字元。

為什麼 JSON 要求雙引號?

JSON 由 Douglas Crockford 在 2001 年定義,標準化為 RFC 8259。語法明確規定只有一種字串定界符:雙引號字元(U+0022)。單引號根本不在語法中。

原因是簡單。JSON 是一種資料交換格式,目的是被任何語言解析,而不只是 JavaScript。規範強制一種引號風格,消除了一整類歧義。Go、Python、Java、Rust 以及任何其他語言的 parser 都實作了完全相同的規則。

JSON vs JavaScript 物件字面量:完整差異

單引號只是諸多差異之一。完整版如下:

1. key 必須是雙引號字串

// ❌ JavaScript 物件字面量 —— 裸 key、單引號 key
{ name: "Ada", 'score': 98 }

// ✅ JSON —— 所有 key 都是雙引號字串
{ "name": "Ada", "score": 98 }

JavaScript 物件字面量接受裸識別字、單引號字串與雙引號字串作為 key。JSON 只接受雙引號字串。

2. 不允許尾隨逗號

// ❌ 非法 JSON
{ "name": "Ada", "score": 98, }
//                            ^ 尾隨逗號

// ✅ 合法 JSON
{ "name": "Ada", "score": 98 }

現代 JavaScript 明確允許尾隨逗號。JSON 不允許 —— 見我們關於 JSON 尾隨逗號的完整指南

3. 沒有註解

// ❌ 非法 JSON
{
  // 使用者個人檔
  "name": "Ada"
}

// ✅ JSON 沒有註解語法
{ "name": "Ada" }

JSON 根本沒有任何註解語法。若你需要在設定檔中加註解,可考慮 JSONC 格式(JSON-with-Comments),VS Code 設定、TypeScript 的 tsconfig.json 及若干設定檔 parser 都支援。

4. 沒有 undefined、函式或 Symbol

// JavaScript 物件 —— 接受不可序列化的值
const obj = {
  fn: () => 42,         // 函式
  sym: Symbol('id'),    // Symbol
  val: undefined,       // undefined
};

// JSON.stringify 會悄悄丟掉或轉換它們
JSON.stringify(obj);
// → '{}'   (這三個屬性都沒了或變成 null)

JSON 恰好支援六種值型別:string、number、boolean(true/false)、null、object 與 array。函式、Symbol 與 undefined 在 JSON 中沒有任何表示法。JSON.stringify() 會默默丟掉物件中含這些值的屬性,並把它們在陣列裡轉成 null

5. 沒有 NaNInfinity

JSON.stringify({ ratio: NaN, limit: Infinity });
// → '{"ratio":null,"limit":null}'
// NaN 與 Infinity 變成 null —— 還是悄悄的!

JSON.parse('{"ratio": NaN}');
// → SyntaxError: Unexpected token 'N'

NaNInfinity 是合法的 JavaScript 數字值,但不屬於 JSON 數字規格。JSON.stringify() 出去時把它們轉成 nullJSON.parse() 進來時則拒絕。這種不對稱常導致隱晦的 bug。

6. 數字:沒有十六進位、沒有前導零、沒有 +

// ❌ 非法 JSON
{ "flags": 0xFF, "code": 007, "delta": +1.5 }

// ✅ 合法 JSON
{ "flags": 255, "code": 7, "delta": 1.5 }

JSON 數字必須為十進位,不能帶顯式的 + 號,也不能有前導零(除了小數點前那一個 0)。十六進位字面量與八進位字面量都不是合法 JSON。

7. 沒有多行字串

// ❌ 非法 JSON —— 字串內含字面換行
{ "bio": "Line one
Line two" }

// ✅ 使用轉義序列
{ "bio": "Line one\nLine two" }

8. 計算屬性名、Symbol 鍵與方法簡寫

另外三個看似 JSON 但在 JSON 中沒有等價的 JS 專屬特性:

// ❌ 計算屬性名 —— 是運算式,不是字面量
const k = "id";
const obj = { [k]: 1, [`user_${k}`]: 1 };
// JSON 沒有運算式;key 必須是字面意義上的雙引號字串。

// ❌ Symbol 鍵 —— 會被 JSON.stringify 默默丟掉
const tag = Symbol('tag');
JSON.stringify({ [tag]: 'admin' });  // → '{}'

// ❌ 方法簡寫 —— 函式會被丟掉/省略
const obj = { greet() { return 'hi'; } };
JSON.stringify(obj);  // → '{}'

若你需要其中任何一項,就把物件序列化成 JSON 形狀的投影 —— JSON.stringify() 永遠只會產出字串 key 與那六種 JSON 值型別。

快速轉換 cheatsheet

JavaScript 物件字面量合法 JSON 等價寫法
{ name: "Ada" }{"name":"Ada"}
{ 'name': 'Ada' }{"name":"Ada"}
{ a: 1, }{"a":1}
{ val: undefined }{}(屬性被丟)
{ n: NaN }{"n":null}
{ n: Infinity }{"n":null}
{ fn: () => 1 }{}(屬性被丟)
0xFF255

把 JS 物件轉成 JSON 的最穩妥方法

切勿透過手動編輯引號把 JavaScript 物件轉成 JSON。用 JSON.stringify() —— 它會正確處理每一種邊界情況:

const obj = { name: 'Ada', score: 98, active: true };

// 緊湊
JSON.stringify(obj);
// → '{"name":"Ada","score":98,"active":true}'

// 帶 2 個空白縮排的 pretty-print
JSON.stringify(obj, null, 2);
// → '{
  "name": "Ada",
  "score": 98,
  "active": true
}'

JSON.stringify() 的第二個引數是一個 replacer —— 你可以傳一個 key 名陣列只包含那些屬性,或傳一個函式來變換值:

// 只包含指定 key
JSON.stringify(obj, ['name', 'score'], 2);
// → '{
  "name": "Ada",
  "score": 98
}'

// 自訂 replacer —— 把 undefined 轉成 null
JSON.stringify({ a: 1, b: undefined }, (key, value) =>
  value === undefined ? null : value
);
// → '{"a":1,"b":null}'

當你拿到一段 JS 物件字面量字串時

有時你拿到的資料看起來像 JSON,其實是 JavaScript 物件字面量 —— 可能來自日誌、除錯工具,或某個未遵守規範的 API。這時 JSON.parse() 會失敗,你有兩個選擇:

  • 用一個修復 parser —— 把單引號轉雙引號、去掉尾隨逗號、為裸 key 加引號,自動處理上述所有差異。對不可信輸入而言這是最穩的方法。
  • eval()Function() —— 技術上對合法 JavaScript 可以,但只要來源不完全可信就是嚴重的安全風險。絕對不要對使用者輸入這麼做。

常見問題

JSON 支援單引號嗎?

不支援。JSON 語法(RFC 8259)只定義一種字串定界符 —— 雙引號。key 與值都必須使用雙引號;單引號會丟 SyntaxError

JSON 與 JavaScript 物件的差異是什麼?

JSON 是嚴格的文字格式;JavaScript 物件字面量是寬鬆的程式碼。JS 允許單引號、不加引號的 key、尾隨逗號、註解、undefinedNaN、函式與十六進位數字 —— 這些都不是合法 JSON。完整 cheatsheet 在上方,入門見 什麼是 JSON?

如何把 JavaScript 物件轉成 JSON?

JSON.stringify() —— 絕不要手動改引號。它能處理每一種邊界情況:丟掉不可序列化的值並正確地轉義字串。

為什麼不能用 eval() 解析 JS 物件字面量?

eval() 會執行任意程式碼,對不可信輸入是嚴重的安全風險。改用修復 parser(或 JSON Fix)安全地把 JS 物件字面量轉成合法 JSON。

立刻修好單引號

若你手上有一段 JavaScript 物件字面量、現在就想轉成合法 JSON,JSON Fix 會自動處理。它會把單引號轉雙引號、為裸 key 加引號、去掉尾隨逗號,並修正上面列出的所有其他差異 —— 都在你的瀏覽器中,不會送往任何伺服器。