【前端冷知识】你会用JS格式化JSON吗?

JSON.stringify是一个基本上所有前端工程师都不陌生的方法。

:point_right|type_1_2: 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在处理数据的时候同时考虑了数据转换和方便阅读,只不过,方便阅读这一点,常常被人忽略。

我们看一下

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” 属性,这样我们就会得到一个只包含学生名不包含成绩的数据:

:bulb:当我们传数组给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’}} 其他的属性都会被过滤掉。

除了传数组,我们还可以传一个函数作为这个参数:

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的成绩。

:point_right|type_1_2: 如果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属性,然后被函数递归地序列化,造成死循环。要避免这种情况,我们只需要修改一下,比如改变一下属性名:

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中讨论。

关于奇舞周刊

《奇舞周刊》是360公司专业前端团队「 奇舞团 」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。