← 全部文章

如何用 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 类型。

立即试用