你真的理解JSON.stringify()吗

创建于

ECMAScript 5定义了JSON对象,对JSON的行为进行了规范,这个对象有两个方法:stringify()parse()
先看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let people = {
name: 'YY',
age: 20,
gender: 'male',
address: {
city: 'xxx',
street: 'linyindadao'
},
skill: ['JavaScript', 'PHP', 'Python', undefined],
phone: undefined
}
JSON.stringify(people, function (key, value) {
if (typeof value !== 'string') {
return undefined
}
return value
})
// 返回值undefined

上面这块代码的本意是只想序列化出值为字符串的属性,但是返回的值是undefined。接下来让我们一探究竟。首先来详细了解一下JSON.stringify()这个方法。

用法和处理规则

JSON.stringify()是将可以把JavaScript对象转化为JSON字符串。接收三个参数:
1.要序列化的JavaScript对象(必须)
2.一个数组或一个函数(可选),可以做序列化筛选和处理
3.指定缩进的空白符(可选)
接下来有必要了解一下序列化的详细规则

1.非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
2.布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
3.所有以 symbol 为属性键的属性都会被完全忽略。
4.不可枚举的属性会被忽略
5.所有函数(toJSON除外)及原型成员都会被忽略

有了上面这个基础,我们接下来一一实践。

序列化筛选过滤

1
2
3
4
5
6
7
8
9
10
// 一个参数
JSON.stringify(people)
// {"name":"YY","age":20,"gender":"male","address":{"city":"xxx","street":"linyindadao"},"skill":["JavaScript","PHP","Python", null]}
// 第二个参数为数组,筛选出key为name和age
JSON.stringify(people, ['name', 'age'])
//{"name":"YY","age":20}

在文章刚开始的例子中,第二个参数是一个函数,它接收两个参数,分别是对象的键和值,键只能是字符串,而在值并非键值对儿结构的值时,键名可以是空字符串,该函数必须要有返回值。但是我们发现返回值是undefined。这是为什么呢,在该函数中将key和value打印出来,发现函数在第一次执行时key为””,value为people整个对象。这里我们就知道原因了,JSON.stringify()时,第一次value是整个对象,在上面的例子中直接被返回undefined了,以至于后续的结果被忽略改变了。这里我们将key,value打印出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
JSON.stringify(people, function (key, value) {
console.log(key, value)
return value
})
/*
结果依次如下:
"" {name: "YY", age: 20, gender: "male", address: {…}, skill: Array(3), phone: undefined}
name "YY"
age 20
gender "male"
address {city: "xxx", street: "linyindadao"}
city "xxx"
street "linyindadao"
skill ["JavaScript", "PHP", "Python", undefined]
0 "JavaScript"
1 "PHP"
2 "Python"
undefined undefined
phone undefined
*/

由上面的打印结果可见,JSON.stringify()是依次遍历每一个值,根据上面的规则做序列化处理。这样我们可以对第一个例子稍作修改,就可达到想要的效果。

1
2
3
4
5
6
7
JSON.stringify(people, function (key, value) {
if (typeof value === 'object' || typeof value === 'string') {
return value
}
return undefined
})
// {"name":"YY","gender":"male","address":{"city":"xxx","street":"linyindadao"},"skill":["JavaScript","PHP","Python",null]}

缩进和空白

JSON.stringify()方法的第三个参数用于控制序列化结果的缩进和空白符。它的参数可以是一个数值,表示每一个级别的缩进的空格数,比如这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JSON.stringify(people, null, 2)
/*
{
"name": "YY",
"age": 20,
"gender": "male",
"address": {
"city": "xxx",
"street": "linyindadao"
},
"skill": [
"JavaScript",
"PHP",
"Python",
null
]
}
*/

这样看起来可读性更好,该数值最大缩进空格数为10,如果大于10会被转换成10。
除此之外,它还可以是一个或多个其他字符组起来的字符串,并且如果字符串的长度超过了10个,那么也只出现前十个字符,比如这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JSON.stringify(people, null, '==')
/*
{
=="name": "YY",
=="age": 20,
=="gender": "male",
=="address": {
===="city": "xxx",
===="street": "linyindadao"
==},
=="skill": [
===="JavaScript",
===="PHP",
===="Python",
====null
==]
}
*/

toJSON()

当JSON.stringify()不能够满足需求的时候,这时候toJSON()就派上用场了。我们可以给对象定义一个toJSON方法,那么在调用JSON.stringify()时不是该对象被序列化,而是toJSON()这个方法的返回值被序列化。

1
2
3
4
5
6
7
let p1 = {
...people,
toJSON () {
return `${this.name}==>${this.skill[0]}`
}
}
// "YY==>JavaScript"

总结

由上面的例子我们可以知道,在将一个对象传给JOSN.stringify()序列化时,顺序如下:

1.如果存在toJSON()方法而且能通过它取得有效的值,则调用该方法。否则,返回对象本身。
2.如果提供了第二个参数,应用这个函数过滤器。传入函数过滤器的值是第1步返回的值。
3.对第2步返回的每个值进行相应的序列化。
4.如果提供了第三个参数,做相应的格式化处理。