JS对象转字符串:JSON.stringify用法与参数详解

作者:闭包旅人

时间:2026-11-03

浏览:360

深入详解 JavaScript 中 JSON.stringify 的完整用法,包括 value、replacer、space 三大参数的机制、差异对比、特殊数据类型转换边界与常见陷阱处理。

文章目录

在 JavaScript 中,将对象转化为可持久化存储或网络传输的字符串,最直接且标准的方法是使用内置的 JSON.stringify()。它不仅能够将常规对象与数组转换为标准 JSON 字符串,还允许通过额外的参数实现属性筛选、脱敏替换、结构定制及缩进排版。

JS对象转字符串:JSON.stringify用法与参数详解
图 1:JSON.stringify 核心处理通道与三参数位置

一、基础语法与调用形式

在日常开发中,前端向后端提交数据、向 localStorage 写入缓存,或者记录运行日志时,都必须将内存中的对象转化为符合规范的文本。最基础的调用只需传入一个参数:

const user = {
  id: 101,
  name: "张三",
  isAdmin: false
};

const jsonStr = JSON.stringify(user);
console.log(jsonStr);
console.log(typeof jsonStr);
控制台输出
{"id":101,"name":"张三","isAdmin":false}
string

尽管单参数调用最为普遍,但 JSON.stringify 的完整方法签名包含三个参数:

JSON.stringify(value[, replacer[, space]])

掌握后面两个可选参数,能让我们直接完成按需取字段、敏感数据掩码以及格式化排版等需求,避免在序列化前手动做浅拷贝或多余的对象重构。

JS对象转字符串:JSON.stringify用法与参数详解
图 2:JSON.stringify 内部串行处理机制步骤拆解

二、参数一:value 与自定义 toJSON 机制

value 是必填项,代表将被序列化的目标值。除了普通对象和数组外,它还可以接收基本类型(字符串、数字、布尔值等)。

在处理 value 时,引擎首先会检查该对象或其原型链上是否声明了 toJSON() 方法。若存在,该方法会被立即执行,并将方法执行后的返回值作为本次序列化的实际输入,而不是直接序列化原对象。

const book = {
  title: "JavaScript 高级程序设计",
  price: 99,
  secretStock: 450,
  toJSON() {
    return {
      title: this.title,
      price: `¥${this.price}`
    };
  }
};

console.log(JSON.stringify(book));
控制台输出
{"title":"JavaScript 高级程序设计","price":"¥99"}

JavaScript 内置的 Date 对象正是利用了这一机制:Date.prototype.toJSON() 内部直接委托给 toISOString(),因此 JSON.stringify(new Date()) 得到的是符合 ISO 8601 标准的日期字符串,而非包含内部时间戳的普通对象。

三、参数二:replacer 的两种形态与关键差异

第二个参数 replacer 是控制序列化细节的核心。它可以是一个过滤数组,也可以是一个转换函数。两种形态在设计目标、控制粒度和执行机制上存在明显差异。

JS对象转字符串:JSON.stringify用法与参数详解
图 3:replacer 函数形态与数组形态机制对比

1. 作为数组:声明式白名单

当 replacer 是一个字符串或数字组成的数组时,只有包含在数组内的属性名才会被序列化,其余属性全部过滤:

const student = {
  name: "小李",
  age: 18,
  grade: "高三",
  token: "secret_xyz_9988"
};

// 仅保留 name 与 age
const filtered = JSON.stringify(student, ["name", "age"]);
console.log(filtered);
控制台输出
{"name":"小李","age":18}
边界提示:白名单仅对对象属性起效。如果被序列化的是一个数组(例如 [student]),白名单不会限制数组外层索引,但仍会对数组内包含的对象属性生效。

2. 作为函数:动态计算与精准过滤

当 replacer 为函数时,函数接收两个参数:key 和 value。引擎会采用深度优先方式递归遍历每一个键值对。

初次调用非常特殊:第一次进入 replacer 时,key 是一个空字符串 "",而 value 是待序列化的整个顶层对象。随后才会依次递归每个子键。

const account = {
  id: 1001,
  username: "hunter",
  passwordHash: "e10adc3949ba59abbe56e057f20f883e",
  balance: 8500.5,
  isVip: true
};

const safeJson = JSON.stringify(account, (key, value) => {
  // 顶层对象时 key 为 "",必须直接返回该 value
  if (key === "passwordHash") {
    return undefined; // 返回 undefined 会剔除该属性
  }
  if (key === "balance") {
    return Math.floor(value); // 将金额取整
  }
  return value; // 其余值原样返回
});

console.log(safeJson);
控制台输出
{"id":1001,"username":"hunter","balance":8500,"isVip":true}

如果函数返回了 undefined,该属性会被完全剔除;如果返回的是其他数据类型,序列化过程将使用该返回值。这为敏感信息抹除和特定数据转化提供了极大的自由度。

四、参数三:space 与格式化排版

第三个参数 space 用于控制结果字符串的排版空白,改善人眼阅读体验(美化输出)。

  • 传入数字:表示每一级缩进的空格数。取值范围为 1 到 10。若小于 1,则视为无缩进;若大于 10,会被自动截断为 10。
  • 传入字符串:该字符串会被直接作为缩进前缀(例如使用制表符 " " 或破折号 "--"),最长取前 10 个字符。
  • 不传或为 null:保持紧凑输出,无任何换行和多余空格,适合网络传输以节约带宽。
const config = { host: "localhost", port: 8080 };

// 缩进 2 个空格
console.log(JSON.stringify(config, null, 2));

// 使用制表符缩进
console.log(JSON.stringify(config, null, "	"));
控制台输出(缩进 2 空格)
{
  "host": "localhost",
  "port": 8080
}

五、特殊类型转换规则与边界限制

并非所有 JavaScript 类型都能直接映射为 JSON 标准类型。在遇到函数、未定义值、特殊数值或非标准容器时,JSON.stringify 会根据其所处的位置采取截然不同的静默处理策略。

JS对象转字符串:JSON.stringify用法与参数详解
图 4:特殊类型与边界条件在不同结构下的转换规则
数据类型 作为对象属性 作为数组元素 单独序列化
undefined 属性直接被忽略(丢失) 转换为 null 返回 undefined
Function 属性直接被忽略(丢失) 转换为 null 返回 undefined
Symbol 属性直接被忽略(丢失) 转换为 null 返回 undefined
NaN / Infinity 转换为 null 转换为 null 返回 "null"
BigInt 抛出 TypeError 抛出 TypeError 抛出 TypeError
循环引用对象 抛出 TypeError 抛出 TypeError 抛出 TypeError

1. 为什么 BigInt 会抛出 TypeError?

JSON 规范中的数字采用双精度浮点数标准,而 BigInt 用于表示任意精度的整数。如果无序隐式转换为普通数字,将导致严重的精度截断问题。因此,JavaScript 引擎选择在遇到 BigInt 时直接抛出 TypeError: Do not know how to serialize a BigInt。

解决办法:在 replacer 中将其显式转换为字符串,或者扩展 BigInt.prototype.toJSON:

const data = { count: 9007199254740993n };

const result = JSON.stringify(data, (key, value) => {
  return typeof value === "bigint" ? value.toString() : value;
});

console.log(result);
控制台输出
{"count":"9007199254740993"}

2. 如何安全处理循环引用?

当对象内部的某个属性直接或间接引用了该对象本身时,序列化过程将陷入无限递归,导致抛出 TypeError: Converting circular structure to JSON:

const nodeA = { name: "节点 A" };
const nodeB = { name: "节点 B" };
nodeA.sibling = nodeB;
nodeB.sibling = nodeA; // 构造相互引用

// 直接执行会报错:
// JSON.stringify(nodeA);

解决方案:利用 WeakSet 记录已经访问过的对象,若重复出现则返回 undefined 打破循环:

function stringifySafely(obj, space = 2) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return "[Circular]"; // 或者返回 undefined 忽略
      }
      seen.add(value);
    }
    return value;
  }, space);
}

console.log(stringifySafely(nodeA));
控制台输出
{
  "name": "节点 A",
  "sibling": {
    "name": "节点 B",
    "sibling": "[Circular]"
  }
}

六、实战场景决策与知识总结

为了让不同开发场景下的参数选型更加清晰,下图梳理了从基础序列化到复杂数据脱敏的决策路径:

JS对象转字符串:JSON.stringify用法与参数详解
图 5:JSON.stringify 实战场景选型与参数组合决策树

综上所述,JSON.stringify() 绝非仅仅是一个单一功能的字符串转换器,而是一个兼具数据过滤、类型拦截与文本排版能力的序列化管道。理解 toJSON 的执行优先权、熟练掌握 replacer 的函数与数组形态差异,并牢记 undefined、BigInt 以及循环引用的边界规则,能够帮助我们在日常开发中写出更加健壮、高效的数据序列化代码。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章