- 教程
【免费下载链接】jstips
This is about useful JS tips!
本篇技术指南围绕 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附加参数的三种能力:
- 数组形式的
replacer:按白名单挑出顶层属性,代码最简洁; - 函数形式的
replacer:以(key, val)形式逐键过滤,支持嵌套对象、支持值改写、支持以return undefined剔除字段; space参数:用数字(空格数)或字符串(如'\t\t')控制缩进,生成可读的美化 JSON。
这三个参数是JSON.stringify最容易被忽略、却最具实战价值的部分。掌握它们之后,无论是日志调试、接口裁剪、还是敏感字段过滤,都可以少写很多手写遍历代码,让序列化这件事变得既精确又优雅。本文对应的原始技巧文档位于仓库 西语版 与 英文版,并可结合 仓库 README 的 Tips 列表 中的第 40 期索引进一步了解整个 jstips 系列。
- 教程
【免费下载链接】jstips
This is about useful JS tips!
相关推荐
DiceDB HEXISTS 命令详解:校验 Hash 字段存在性与条件逻辑实战
DiceDB HEXISTS 命令详解:校验 Hash 字段存在性与条件逻辑实战 HEXISTS 是 DiceDB 中用于校验指定 key 所存储的 Hash(
教程jstips 第 40 期解读:掌握 JSON.stringify 的附加参数,按需序列化对象属性
jstips 第 40 期解读:掌握 JSON.stringify 的附加参数,按需序列化对象属性 JSON.stringify 是 JavaScript 中最
教程30-seconds-of-code:使用 JSON.stringify 的第二参数实现对象键的选择性序列化
30 seconds of code:使用 JSON.stringify 的第二参数实现对象键的选择性序列化 JSON.stringify 是 JavaScri
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考