← 全部文章

如何用 JSON.stringify 序列化 JSON

JSON.stringify 把一個值轉成 JSON 字串。學習 space 與 replacer 引數、toJSON 鉤子,以及它會默默丟掉或丟錯的值。

JSON.stringify() 把一個 JavaScript 值轉換為 JSON 字串 —— 用於放進請求主體、儲存到檔案,或存到 localStorage。它看起來簡單,但它的三個參數、對特殊值的處理,以及 toJSON 掛勾,三不五時就把開發者絆倒。本指南涵蓋 JSON.stringify() 所做的一切、其他語言中對應的做法,以及需要避開的陷阱。

JSON.stringify 做了什麼

它遞迴地走過一個值,產出符合規範的 JSON 字串。預設輸出是緊湊的,不帶多餘空白:

const user = { name: "Ada", age: 36, active: true };

JSON.stringify(user);
// '{"name":"Ada","age":36,"active":true}'

因為它始終輸出合法 JSON,所以你應該永遠不要手寫拼接 JSON 字串 —— 用 concat 或樣板字面值都不行,這是 [object Object] 錯誤尾隨逗號錯誤 的頭號原因。

三個參數

JSON.stringify(value, replacer, space) 接收一個值,加上兩個多數開發者從不使用、但其實很強大的選擇性參數。

space —— 美化輸出

第三個參數控制縮排。傳一個數字表示空格數,或傳字串例如 '\t' 表示 Tab:

JSON.stringify(user, null, 2);
// {
//   "name": "Ada",
//   "age": 36,
//   "active": true
// }

這就是在程式碼中美化 JSON 的方式 —— 見 如何格式化 JSON。省略該參數(或傳 0)即可得到最小化輸出;見 如何最小化 JSON

replacer —— 過濾與變換

第二個參數可以是要保留的鍵陣列,或一個變換每個值的函式:

// 白名單:只保留這些鍵
JSON.stringify(user, ['name', 'active']);
// '{"name":"Ada","active":true}'

// 函式:脫敏一個欄位、捨棄另一個
JSON.stringify(
  { name: "Ada", password: "secret", token: "abc" },
  (key, value) => {
    if (key === 'password') return '[REDACTED]';
    if (key === 'token') return undefined; // 回傳 undefined 會去掉該鍵
    return value;
  },
);
// '{"name":"Ada","password":"[REDACTED]"}'

toJSON 掛勾

如果一個值有 toJSON() 方法,JSON.stringify() 會呼叫它並改為序列化回傳值。這就是為什麼 Date 物件會自動變成 ISO 字串:

JSON.stringify({ when: new Date('2026-05-24T00:00:00Z') });
// '{"when":"2026-05-24T00:00:00.000Z"}'  ← Date.prototype.toJSON 跑了

// 自訂 toJSON 實作自訂序列化
class Money {
  constructor(cents) { this.cents = cents; }
  toJSON() { return (this.cents / 100).toFixed(2); }
}
JSON.stringify({ price: new Money(1999) });
// '{"price":"19.99"}'

什麼會被丟掉或轉換

JSON 只有六種型別,因此 JavaScript 中沒有 JSON 對等物的值會被靜默捨棄或強轉 —— 這是往返 bug 的常見來源:

JavaScript 值JSON.stringify 的結果
undefined(在物件裡)鍵被省略
undefined(在陣列裡)null
函式省略(物件)/ null(陣列)
Symbol省略
NaNInfinitynull
DateISO 字串(透過 toJSON
BigInt拋出 TypeError

關於 JSON 與 JavaScript 物件差異的完整列表,見 JSON vs JavaScript 物件

常見陷阱

循環參照會拋錯

const a = {};
a.self = a;
JSON.stringify(a);
// TypeError: Converting circular structure to JSON

// 修復:記錄已見物件的 replacer,或重構資料結構

不支援 BigInt

JSON.stringify({ id: 9007199254740993n });
// TypeError: Do not know how to serialize a BigInt

// 修復:先轉為字串,或加一個 replacer
JSON.stringify({ id: 9007199254740993n }, (k, v) =>
  typeof v === 'bigint' ? v.toString() : v
);

大整數在往返中遺失精度

超過 253 的數字無法精確表示。若你 stringify 後又 parse 一個大整數 ID,它可能會改變。這類 ID 請存為字串。

相鄰 API:structuredClonereviver,以及穩定 stringify

  • structuredClone —— 做記憶體深複製時不要用 JSON.parse(JSON.stringify(x))structuredClone 原生處理 DateMapSetArrayBuffer 以及循環參照,而且快得多。
  • 傳給 JSON.parsereviver 參數 與這裡的 replacer 對稱。當你需要對 JSON 不建模的型別做無損往返時,把它們成對使用 —— 例如在 replacer 裡把 Date 編碼為 ISO 字串,再在 reviver 裡復原回來。
  • 穩定 stringify 函式庫(例如 json-stable-stringify fast-json-stable-stringify)—— 無論鍵的插入順序如何,都產出相同的字串。當輸出被雜湊、簽章或作為文字比較時使用(正式版本見 RFC 8785 / JCS)。

其他語言中的 Stringify

# Python
import json
json.dumps({"name": "Ada"})                       # 緊湊
json.dumps({"name": "Ada"}, indent=2)             # 美化
json.dumps({"name": "Ada"}, separators=(',', ':')) # 最小化

// Go
import "encoding/json"
b, _ := json.Marshal(map[string]string{"name": "Ada"})

// Ruby
require 'json'
{ name: "Ada" }.to_json

在你的瀏覽器中 Stringify JSON

需要把一個 JSON 值跳脫為字串字面值 —— 用於嵌入程式碼、資料庫欄位或 shell 命令?fixjson 的 JSON Stringify 工具 即時完成,且在你的瀏覽器中執行,資料不會傳到任何伺服器。

常見問題

怎樣用 JSON.stringify 美化輸出?

傳第三個參數:JSON.stringify(value, null, 2) 表示兩空格縮排,或 '\t' 表示 Tab。見 如何格式化 JSON

為什麼 JSON.stringify 丟掉了一些屬性?

值為 undefined、函式或 Symbol 的屬性會被省略,因為 JSON 沒有對應的表示方式。請用 replacer 或先做轉換。

怎麼 stringify 含 BigInt 的值?

JSON.stringify() 遇到 BigInt 會拋錯。請提供一個對 bigint 值呼叫 .toString() 的 replacer,並在另一端有意識地把它們解析回來。

JSON.stringify 與 JSON.parse 有什麼差別?

stringify 把值變成 JSON 字串;parse 把 JSON 字串還原為值。兩者互逆。解析中的陷阱見 在 JavaScript 中處理壞掉的 JSON

怎麼 JSON.stringify 一個 Map 或 Set?

直接序列化的話,兩者都會變成 "{}" —— Map 沒有可列舉的自有屬性,而 Set 被序列化為空物件。請先把它們轉成 JSON 友善的形態(或者用 replacer):

// Map → [key, value] 對的陣列(可往返)
JSON.stringify([...myMap]);
// 或者用 replacer:
JSON.stringify({ data: myMap }, (k, v) =>
  v instanceof Map ? Object.fromEntries(v) :
  v instanceof Set ? [...v] : v
);

回程時在 JSON.parse 的 reviver 裡用 new Map(arr) / new Set(arr) 重建,因為純 JSON 沒有 Map/Set 型別。

立即試用