JavaScript toJSON 的位置参数:同一个对象,放进根、字段和数组为什么输出不同

前天 3阅读

给对象定义 toJSON 后,JSON.stringify 使用的是这个方法返回的表示。这个钩子还会收到一个 key 参数:同一个对象直接导出、放到 payload 字段里、或放到数组里,收到的位置名称各不相同。如果方法根据 key 决定结构,移动对象的位置就能改变导出的格式。

本例在 Node.js v24.19.0 上运行。保存为 demo.mjs,执行 node demo.mjs。所有输入都是程序自己创建的小对象。toJSON 返回一个新的普通对象,额外写入 at 字段,让收到的 key 直接出现在结果中。

JavaScript toJSON 的位置参数:同一个对象,放进根、字段和数组为什么输出不同

AI模型生成概念示意:同一个几何对象分别进入独立框、嵌套框和数组槽位,并获得不同位置标签;这是序列化协议的比喻,不是对象内存结构图。

完整实验

const record = {
  value: 7,
  toJSON(key) {
    return { at: key, value: this.value };
  }
};

console.log('root', JSON.stringify(record));
console.log('property', JSON.stringify({ payload: record }));
console.log('array', JSON.stringify([record]));
console.log('empty-property', JSON.stringify({ '': record }));

const seen = [];
const result = JSON.stringify({ payload: record }, (key, value) => {
  if (key === 'payload') seen.push(Object.keys(value));
  return value;
});
console.log('replacer-sees', JSON.stringify(seen));
console.log('result', result);
console.log('original', record.value, typeof record.toJSON);

try {
  JSON.stringify({ toJSON() { throw new Error('stop-export'); } });
} catch (error) {
  console.log('failure', error.message);
}

本地实际输出

root {"at":"","value":7}
property {"payload":{"at":"payload","value":7}}
array [{"at":"0","value":7}]
empty-property {"":{"at":"","value":7}}
replacer-sees [["at","value"]]
result {"payload":{"at":"payload","value":7}}
original 7 function
failure stop-export

root 的 at 是空字符串。property 中的 at 是 payload,array 中则是字符串 "0"。索引在这个协议里仍以字符串提供,不是数字零。修改包装结构时,toJSON 能感知的就是当前这一层的键名,不能凭它自动获得完整路径。

empty-property 特别验证了一个边界:真正名为 "" 的字段也会传入空字符串。因此 key === "" 不能单独证明当前一定是根对象。如果业务需要不同导出场景,最好显式提供专门的导出函数或配置,而不是把所有场景偷偷编码到位置名称里。

replacer-sees 输出的是 at、value 两个键,说明 replacer 遇到 payload 时,拿到的已经是 toJSON 的返回对象。它不会先看到原对象再决定是否执行这个方法。original 一行仍为 7 function,是因为本例钩子只创建新对象,没修改原始 record;语言并不替其他有副作用的钩子提供这项保证。

边界与使用约定

failure 一行来自主动抛错的 toJSON。JSON.stringify 会把这个错误传给调用者,外层 catch 才能决定中止导出或记录失败。不能因为对象“只是被转成文字”,就假定序列化不会执行程序代码,也不能把陌生对象的 toJSON 当成纯数据字段。

位置依赖设计适合非常明确、测试充分的内部协议。若同一个对象会出现在多种接口或日志中,更容易维护的做法是让 toJSON 返回稳定结构,在进入序列化之前决定所需字段。这样重命名 payload 或把对象移到数组里,不会顺带改变核心数据含义。

验收时至少同时测试根位置、普通字段、数组成员与空字段名,并单独测试方法抛错。本文聚焦 key 和调用先后,不以 JSON 往返作为深拷贝,也不把自定义序列化钩子等同于原对象本身。

参考资料

官方资料核验日期:2026-10-02。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。