Open akira-cn opened 5 years ago
JSON.stringify是一个基本上所有前端工程师都不陌生的方法。
👉🏻 JSON(JavaScript Object Notation)是由ECMA标准化(ECMA-404)的一种轻量级的数据交换格式。它来源于ECMAScript,是一种便于人阅读和理解的数据交换格式,目前被几乎所有的主流语言和平台支持。
JSON.stringify和JSON.parse是一对处理数据的常用方法,前者将JavaScript的对象转为JSON字符串,后者将一个JSON串解析为JavaScript的对象。
const obj = {foo: 'bar'}; const str = JSON.stringify(obj); // {"foo":"bar"} console.log(str); const obj2 = JSON.parse(str); console.log(obj2); // [Object object]{foo: "bar"}
JSON作为数据格式,既能被平台处理,又能方便人阅读,JavaScript的JSON.stringify在处理数据的时候同时考虑了数据转换和方便阅读,只不过,方便阅读这一点,常常被人忽略。
我们看一下
<textarea id="show-score"></textarea>
const students = [ { name: 'akira', score: 100, }, { name: 'John', score: 60, }, { name: 'Jane', score: 90, } ]; const textarea = document.getElementById('show-score'); textarea.textContent = JSON.stringify(students);
上面的代码,我们将学生成绩单显示在页面上一个textarea里,如果我们直接将students通过JSON.stringify转成字符串显示,它是不包含空白符的,不方便人阅读。
在JSON.stringify方法一共能接受3个参数,其中两个可选的参数,最后一个参数是用来格式化显示的,我们看一下:
const students = [ { name: 'akira', score: 100, }, { name: 'John', score: 60, }, { name: 'Jane', score: 90, } ]; const textarea = document.getElementById('show-score'); textarea.textContent = JSON.stringify(students, null, 4);
我们暂时不管stringify的第二个参数,看一下它的第三个参数,在这里我们将它设为4,表示用每行缩进4个空格的格式来格式化stringify后的字符串:
我们也可以用字符串而不是数字来传这个参数,如果用字符串,则表示用该字符串作为占位符缩进:
textarea.textContent = JSON.stringify(students, null, '\t');
除了设置缩进格式和占位符,我们还有更灵活的控制方式,我们看一下:
const students = [ { name: 'akira', score: 100, }, { name: 'John', score: 60, }, { name: 'Jane', score: 90, } ]; const textarea = document.getElementById('show-score'); textarea.textContent = JSON.stringify(students, ["name"], 4);
上面的代码,我们传一个["name"]数组给stringify方法,表示只序列化对象中的"name"属性,这样我们就会得到一个只包含学生名不包含成绩的数据:
["name"]
"name"
💡当我们传数组给stringify的第二个参数时,其中的内容表示可序列化的属性名,而且这个设定是递归的,比如:
const data = { name: { name: 'aa', foo: 'bar', }, foo: 'bar', other: { name: 'bb', }, } const result = JSON.stringify(data, ["name"]); console.log(result); // {name: {name: 'aa'}}
比如上面的代码,最后只会序列化{name: {name: 'aa'}} 其他的属性都会被过滤掉。
{name: {name: 'aa'}}
除了传数组,我们还可以传一个函数作为这个参数:
const students = [ { name: 'akira', score: 100, }, { name: 'John', score: 60, }, { name: 'Jane', score: 90, } ]; function replacer(key, value) { if(key === 'score') { if (value === 100) { return 'S'; } else if(value >= 90) { return 'A'; } else if(value >= 70) { return 'B'; } else if(value >= 50) { return 'C'; } else { return 'E'; } } return value; } const textarea = document.getElementById('show-score'); textarea.textContent = JSON.stringify(students, replacer, 4);
上面的代码,我们通过replacer将成绩从百分制替换为SABCE的成绩。
👉🏻 如果stringify的第二个参数为函数那么它的返回值如果是undefined,那么对应的属性不会被序列化,如果返回其他的值,那么用返回的值替代原来的值进行序列化。
如果这个函数返回一个对象,那么这个对象也会用同样的replacer递归地序列化。
const students = [ { name: 'akira', score: 100, }, { name: 'John', score: 60, }, { name: 'Jane', score: 90, } ]; function replacer(key, value) { if(key === 'score') { if (value === 100) { return {score: value, level: 'S'}; } else if(value >= 90) { return {score: value, level: 'A'}; } else if(value >= 70) { return {score: value, level: 'B'}; } else if(value >= 50) { return {score: value, level: 'C'}; } else { return {score: value, level: 'E'}; } } return value; } const textarea = document.getElementById('show-score'); textarea.textContent = JSON.stringify(students, replacer, 4);
上面的代码会导致堆栈溢出,因为return {score: value, level: 'S'}返回的对象中依然还有score属性,然后被函数递归地序列化,造成死循环。要避免这种情况,我们只需要修改一下,比如改变一下属性名:
return {score: value, level: 'S'}
const students = [ { name: 'akira', score: 100, }, { name: 'John', score: 60, }, { name: 'Jane', score: 90, } ]; function replacer(key, value) { if(key === 'score') { if (value === 100) { return {point: value, level: 'S'}; } else if(value >= 90) { return {point: value, level: 'A'}; } else if(value >= 70) { return {point: value, level: 'B'}; } else if(value >= 50) { return {point: value, level: 'C'}; } else { return {point: value, level: 'E'}; } } return value; } const textarea = document.getElementById('show-score'); textarea.textContent = JSON.stringify(students, replacer, 4);
上面代码将返回的对象的score属性名替换成point就可以了。
与 JSON.stringify 对应,JSON.parse 也有一个额外的参数,可以传一个函数:
const str = '{"result":true, "count":42}'; const result = JSON.parse(str, (key, value) => { if(typeof value === 'number') return value * 2; return value; }); console.log(result); // {result: true, count: 84}
所以,JavaScript可以更灵活地处理JSON。这些高级的参数,你平时使用过吗?关于JSON,还有什么其他内容想要讨论,欢迎在issue中讨论。
JSON.stringify 面对属性里面包含自身的对象有什么优雅的处理方法吗
@q269384828 没啥好办法,JSON.stringify设计时就是不考虑circular的。你需要structured clone(目前不在js标准里)。
JSON.stringify是一个基本上所有前端工程师都不陌生的方法。
👉🏻 JSON(JavaScript Object Notation)是由ECMA标准化(ECMA-404)的一种轻量级的数据交换格式。它来源于ECMAScript,是一种便于人阅读和理解的数据交换格式,目前被几乎所有的主流语言和平台支持。
JSON.stringify和JSON.parse是一对处理数据的常用方法,前者将JavaScript的对象转为JSON字符串,后者将一个JSON串解析为JavaScript的对象。
JSON作为数据格式,既能被平台处理,又能方便人阅读,JavaScript的JSON.stringify在处理数据的时候同时考虑了数据转换和方便阅读,只不过,方便阅读这一点,常常被人忽略。
我们看一下
上面的代码,我们将学生成绩单显示在页面上一个textarea里,如果我们直接将students通过JSON.stringify转成字符串显示,它是不包含空白符的,不方便人阅读。
在JSON.stringify方法一共能接受3个参数,其中两个可选的参数,最后一个参数是用来格式化显示的,我们看一下:
我们暂时不管stringify的第二个参数,看一下它的第三个参数,在这里我们将它设为4,表示用每行缩进4个空格的格式来格式化stringify后的字符串:
我们也可以用字符串而不是数字来传这个参数,如果用字符串,则表示用该字符串作为占位符缩进:
除了设置缩进格式和占位符,我们还有更灵活的控制方式,我们看一下:
上面的代码,我们传一个
["name"]
数组给stringify方法,表示只序列化对象中的"name"
属性,这样我们就会得到一个只包含学生名不包含成绩的数据:💡当我们传数组给stringify的第二个参数时,其中的内容表示可序列化的属性名,而且这个设定是递归的,比如:
比如上面的代码,最后只会序列化
{name: {name: 'aa'}}
其他的属性都会被过滤掉。除了传数组,我们还可以传一个函数作为这个参数:
上面的代码,我们通过replacer将成绩从百分制替换为SABCE的成绩。
👉🏻 如果stringify的第二个参数为函数那么它的返回值如果是undefined,那么对应的属性不会被序列化,如果返回其他的值,那么用返回的值替代原来的值进行序列化。
如果这个函数返回一个对象,那么这个对象也会用同样的replacer递归地序列化。
上面的代码会导致堆栈溢出,因为
return {score: value, level: 'S'}
返回的对象中依然还有score属性,然后被函数递归地序列化,造成死循环。要避免这种情况,我们只需要修改一下,比如改变一下属性名:上面代码将返回的对象的score属性名替换成point就可以了。
扩展
与 JSON.stringify 对应,JSON.parse 也有一个额外的参数,可以传一个函数:
所以,JavaScript可以更灵活地处理JSON。这些高级的参数,你平时使用过吗?关于JSON,还有什么其他内容想要讨论,欢迎在issue中讨论。