☰
jstips 系列第 40 期:用 JSON.stringify 选择性序列化与美化输出对象
2026/10/8 1:32:16 网站建设 项目流程
  • 教程

【免费下载链接】jstips

This is about useful JS tips!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载

本篇技术指南围绕 jstips 仓库 第 40 期技巧《Usando JSON.Stringify》(英文版见 _posts/en/javascript/2016-02-09-using-json-stringify.md)展开,核心讲解JSON.stringify的第二个参数replacer(数组或函数)如何实现“只挑选指定属性序列化”,以及第三个参数space如何控制缩进格式。读完你将掌握:用数组白名单过滤对象字段、用函数做键级精细裁剪(甚至替换值)、以及按需生成带缩进的可读 JSON 文本,从而在日志输出、接口返回裁剪、调试打印等场景中写出更简洁、更可控的序列化代码。

一、JSON.stringify的三个参数与“附加参数”的来历

在日常开发中,JSON.stringify(obj)是最常见的用法:把一个 JavaScript 对象转成 JSON 字符串。但正如本 tip 开头所说——当对象上带有prop1、prop2、prop3等多个属性时,我们“可以通过传递附加参数给JSON.stringify来选择性地将属性生成字符串”。

JSON.stringify的完整签名是:

JSON.stringify(value[, replacer[, space]])
  • value:要序列化的对象或值;
  • replacer(附加参数之一):可以是属性名数组,也可以是函数,用于控制序列化过程中包含哪些属性、以及属性值如何被改写;
  • space(附加参数之二):可以是数字或字符串,用于控制输出字符串的缩进与美化格式。

本 tip 的全部内容,正是围绕这两个“附加参数”展开的:先演示数组形式的属性选择,再升级为函数形式的键级过滤,最后展示第三个参数对输出格式的修饰。

二、用数组作为replacer:按白名单选择性序列化

假设有一个对象,包含prop1、prop2、prop3三个属性。如果我们只想把其中两个属性写入 JSON 字符串,可以像下面这样传入一个属性名数组:

var obj = { 'prop1': 'value1', 'prop2': 'value2', 'prop3': 'value3' }; var selectedProperties = ['prop1', 'prop2']; var str = JSON.stringify(obj, selectedProperties); // str // {"prop1":"value1","prop2":"value2"}

运行后得到的str将只包含被选中属性的信息:

{"prop1":"value1","prop2":"value2"}

这里selectedProperties数组相当于一张白名单:只有数组中列出的属性名才会被序列化,其余属性(如prop3)会被直接忽略。

几个值得注意的细节:

  • 数组中元素的顺序,会影响输出 JSON 中键的顺序;
  • 数组只接受字符串或数字类型的属性名,其他类型会被忽略;
  • 如果数组中出现对象上不存在的属性名,该名称会被直接跳过,不会报错;
  • 数组方式对嵌套对象同样生效吗?不完全生效——数组只会作用于顶层对象,replacer数组中的名称会按“从根到叶的每一层”匹配,但要精确裁剪嵌套结构,更可靠的方式是使用函数形式的replacer(见下一节)。

这种“白名单”思想在仓库中并非孤例:第 70 期技巧《Picking and rejecting object properties》 用pick/reject两个函数实现了对对象属性的选择与剔除,其中pick(row, ['client.name'])的用法与本 tip 的数组白名单如出一辙,可以对照阅读,理解“按需取字段”这一模式在序列化与普通对象操作中的两种落地形态。

三、用函数作为replacer:键级过滤与值改写

数组只能做“属性名集合”级别的过滤,无法表达更复杂的逻辑(例如按属性值的类型、按键名模式、或对某个值做特殊改写)。本 tip 随即给出第二种用法:把函数作为第二个参数。

function selectedProperties(key, val) { // 第一次调用时,val 是整个对象,key 是空字符串 if (!key) { return val; } if (key === 'prop1' || key === 'prop2') { return val; } return; }

使用时:

var str = JSON.stringify(obj, selectedProperties); // {"prop1":"value1","prop2":"value2"}

3.1 函数形式的调用机制

当replacer是函数时,序列化过程中每个键值对都会被调用一次,函数接收两个参数:

参数含义第一次调用的特殊值
key当前属性的键名空字符串""
val当前属性的值整个根对象

所以上面的函数第一行if (!key) { return val; }是必不可少的“保险丝”:在第一次调用(key 为空字符串)时把整个对象原样返回,否则对象会被整个丢弃。

之后的每次调用中:

  • 返回val,表示保留该属性;
  • 返回undefined(代码中直接return;),表示删除该属性;
  • 返回其他任意值,则表示用该值替换当前属性值,这正是函数形式比数组更强大的地方——你可以在序列化过程中对值做加工,例如脱敏、类型转换、格式化日期等。

3.2 从源码/规范层面的行为细节

结合 ECMAScript 规范与 Node.js 的实际实现(本仓库为纯文档型 jstips 集合,无运行时代码,但该行为在各大 JS 引擎中一致):

  • 返回值处理规则:若返回undefined或返回一个函数,则该属性被跳过;
  • 对Number值:若返回的数值本身也是NaN、Infinity,会被序列化为null;
  • 对嵌套对象:replacer函数会递归地作用到每一层属性上(而数组形式在嵌套上的控制力较弱),因此函数形式是处理深层对象裁剪的首选;
  • 注意this上下文:调用replacer时,this指向当前正在序列化的对象(即该属性所在的对象),可以利用这一点实现按所在层级做不同处理。

把上面的过滤函数稍作扩展,即可实现“剔除值为函数或undefined的字段”等常见诉求:

function sanitize(key, val) { if (!key) return val; // 保留根对象 if (typeof val === 'function') return undefined; // 函数值跳过 if (val === undefined) return undefined; // undefined 跳过 return val; }

四、第三个参数space:控制输出的缩进与可读性

本 tip 指出:“最后一个可选参数,可以修改生成字符串的方式。”也就是第三个参数space。它接受两类值:

  • 数字:代表每层缩进的空格数(上限 10,超过按 10 处理,最小为 0 表示不缩进);
  • 字符串:直接作为每一层的缩进前缀(长度最多取前 10 个字符)。

4.1 传入制表符字符串

本 tip 的示例传入的是双制表符字符串:

var str = JSON.stringify(obj, selectedProperties, '\t\t'); /* str 每行都以双制表符缩进,输出如下: { "prop1": "value1", "prop2": "value2" } */

注意输出注释中呈现的每行缩进,正是'\t\t'(两个制表符)的效果;从仓库原文的注释可以看到,每个键值对独占一行,且外层大括号与内层属性之间有统一的双制表符缩进。

4.2 传入数字

var str = JSON.stringify(obj, selectedProperties, 2); /* 输出: { "prop1": "value1", "prop2": "value2" } */

传入2时,每层缩进两个空格,这是最常用的美化格式。

4.3 不传或传null的行为

  • 省略第三个参数(或传入null、undefined):输出为紧凑单行格式,即最开始的{"prop1":"value1","prop2":"value2"};
  • 传入0或负数:同样不产生缩进。

space参数最大的价值在于可读性:调试打印、日志输出、测试断言、以及把 JSON 展示给用户/写入配置文件时,带缩进的格式化输出能显著降低阅读成本。本仓库 POST_TEMPLATE.md 中layout: post等元信息即采用 YAML 风格呈现,读者可体会结构化、可读格式在内容组织中的价值。

五、完整组合:三个参数协同工作

把本 tip 的三个示例串起来,一个完整的“选择性序列化 + 美化输出”示例就是:

var obj = { 'prop1': 'value1', 'prop2': 'value2', 'prop3': 'value3' }; // 方式一:数组白名单 + 数字缩进 var str1 = JSON.stringify(obj, ['prop1', 'prop2'], 2); // 方式二:函数过滤 + 双制表符缩进 function selectedProperties(key, val) { if (!key) return val; // 保留根对象 if (key === 'prop1' || key === 'prop2') return val; return; // 其余属性一律跳过 } var str2 = JSON.stringify(obj, selectedProperties, '\t\t'); console.log(str1); console.log(str2);

两种方式输出语义相同,区别在于:数组方式代码更简洁、适合“固定白名单”;函数方式更灵活、可处理嵌套与值改写。实践中可按需选择。

六、应用场景与相关技巧对照

6.1 日志与调试

在日志里打印大型对象时,先用白名单挑出关键字段,再用space: 2美化,既避免刷屏又保证可读:

console.log(JSON.stringify(user, ['id', 'name', 'email'], 2));

6.2 接口返回裁剪与敏感字段剔除

向客户端返回数据前,用函数形式的replacer剔除password、token等敏感字段:

function safeReplacer(key, val) { if (!key) return val; if (key === 'password' || key === 'token') return undefined; return val; }

这一思路与仓库 第 39 期技巧《Advanced Properties》 中提到的“enumerable为 false 的属性不会出现在JSON.stringify结果中”互为补充:一个是在序列化阶段主动过滤,一个是在属性定义阶段控制可枚举性,两者结合可构建更完善的字段可见性控制方案。

6.3 与toJSON的配合(进阶)

若对象本身定义了toJSON()方法,JSON.stringify会优先调用它并用其返回值作为序列化对象,此时replacer数组/函数仍可叠加生效。这是官方规范行为,可用于自定义对象的序列化视图——但需注意,toJSON必须返回可序列化类型的值。

七、小结

本 tip 用三个代码示例完整展示了JSON.stringify附加参数的三种能力:

  1. 数组形式的replacer:按白名单挑出顶层属性,代码最简洁;
  2. 函数形式的replacer:以(key, val)形式逐键过滤,支持嵌套对象、支持值改写、支持以return undefined剔除字段;
  3. space参数:用数字(空格数)或字符串(如'\t\t')控制缩进,生成可读的美化 JSON。

这三个参数是JSON.stringify最容易被忽略、却最具实战价值的部分。掌握它们之后,无论是日志调试、接口裁剪、还是敏感字段过滤,都可以少写很多手写遍历代码,让序列化这件事变得既精确又优雅。本文对应的原始技巧文档位于仓库 西语版 与 英文版,并可结合 仓库 README 的 Tips 列表 中的第 40 期索引进一步了解整个 jstips 系列。

  • 教程

【免费下载链接】jstips

This is about useful JS tips!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载
上一篇:Telepresence 环境变量导入指南:将集群容器环境变量带入本地开发
下一篇:node-emoji完全指南:让Node.js项目轻松集成表情符号支持

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询