JSON.parse() && JSON.stringify()

JSON.parse() && JSON.stringify()

对于这两个方法,在以前的工作当作也仅仅只是使用,但是并没有去深入研究,只是简单的知道一个是将对象或者数组转为 JSON 字符串,另外一个就是反过来转换 JSON 的,但是深入了解以后却发现东西还是有点多的,特别是还支持第二个参数配置,于是便查阅资料深入了解一下,在这里记录记录

顺便安利一个扩展知识 JSON 解析器

JSON.parse()

JSON.parse() 用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象,提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换

1
2
3
4
var json = '{"result": true, "count": 42}'

// { result: true, count: 42 }
JSON.parse(json)

语法如下

1
JSON.parse(text[, reviver])

如果被解析的 JSON 字符串是非法的,则会抛出一个语法错误异常(需要注意,JSON.parse() 不允许最后一个键值对后面存在逗号),如果指定了 reviver 函数,则解析出的 JavaScript 值(解析值)会经过一次转换后才将被最终返回(返回值)

意思就是,解析值本身以及它所包含的所有属性,会按照一定的顺序(从最最里层的属性开始,一级级往外,最终到达顶层,也就是解析值本身)分别的去调用 reviver 函数,在调用过程中,当前属性所属的对象会作为 this 值,当前属性名和属性值会分别作为第一个和第二个参数传入 reviver 中,如果 reviver 返回 undefined,则当前属性会从所属对象中删除,如果返回了其他值,则返回的值会成为当前属性新的属性值

需要注意的是

  • 当遍历到最顶层的值(解析值)时,传入 reviver 函数的参数会是空字符串 ""(因为此时已经没有真正的属性)和当前的解析值(有可能已经被修改过了)
  • 当前的 this 值会是 {"": 修改过的解析值},在编写 reviver 函数时,要注意到这个特例(这个函数的遍历顺序依照从最内层开始,按照层级顺序,依次向外遍历)

下面是两个示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 如果到了最顶层,则直接返回属性值,否则将属性值变为原来的 2 倍
JSON.parse('{"p": 5}', function (k, v) {
if(k === '') return v
return v * 2
})

// { p: 10 }

JSON.parse(' {"1": 1, "2": 2, "3": {"4": 4, "5": {"6": 6}}} ', function (k, v) {
// 输出当前的属性名,从而得知遍历顺序是从内向外的
console.log(k)

// 最后一个属性名会是个空字符串
// 返回原始属性值,相当于没有传递 reviver 参数
return v
})

// 1
// 2
// 4
// 6
// 5
// 3
// ""

JSON.stringify()

JSON.stringify() 的总体用法可以参考上图,下面我们慢慢来看,该方法可以将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串,如果指定了 replacer 是一个函数,则可以替换值,或者如果指定了 replacer 是一个数组,可选的仅包括指定的属性,基本语法如下

1
JSON.stringify(value[, replacer [, space]])

关于可选参数 replacer

  • 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理
  • 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中
  • 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化

关于可选参数 space

  • 指定缩进用的空白字符串,用于美化输出(pretty-print
  • 如果参数是个数字,它代表有多少的空格(上限为 10),该值若小于 1,则意味着没有空格
  • 如果该参数为字符串(字符串的前十个字母),该字符串将被作为空格
  • 如果该参数没有提供(或者为 null)将没有空格

关于序列化,有下面五点注意事项

  • 非数组对象的属性『不能保证以特定的顺序』出现在序列化后的字符串中
  • 布尔值、数字、字符串的包装对象在序列化过程中会『自动转换成对应的原始值』
  • undefined、任意的函数以及 Symbol 值,如果出现在非数组对象的属性值中时,在序列化过程中会被忽略,如果出现在数组中时将被转换成 null,此外 NaNInfinity 格式的数值及 null 也都会被当做 null 来进行处理
  • 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误,这也就是为什么去实现深拷贝时,遇到循环引用的对象会抛出错误的原因
  • 所有以 Symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们
  • 其他类型的对象,包括 Map/Set/WeakMap/WeakSet 仅会序列化可枚举的属性,也就是说『不可枚举的属性会被忽略』

所以我们在使用该方法进行对象拷贝的时候就需要注意了,下面来看几个示例加深一下印象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
JSON.stringify({})                   // '{}'
JSON.stringify(true) // 'true'
JSON.stringify('foo') // '"foo"'
JSON.stringify([1, 'false', false]) // '[1, "false", false]'
JSON.stringify({ x: 5 }) // '{"x": 5}'

JSON.stringify(NaN) // 'null'
JSON.stringify(null) // 'null'
JSON.stringify(Infinity) // 'null'

// "{"x": 5, "y": 6}"
JSON.stringify({ x: 5, y: 6 })

// '[1, "false", false]'
JSON.stringify([new Number(1), new String('false'), new Boolean(false)])

// '{}'
JSON.stringify({ x: undefined, y: Object, z: Symbol('') })

// '[null, null, null]'
JSON.stringify([undefined, Object, Symbol('')])

// '{}'
JSON.stringify({ [Symbol('foo')]: 'foo' })

// '{}'
JSON.stringify({ [Symbol.for('foo')]: 'foo' }, [Symbol.for('foo')])

// undefined
JSON.stringify(
{ [Symbol.for('foo')]: 'foo' },
function (k, v) {
if (typeof k === 'symbol') {
return 'a symbol'
}
}
)

// 不可枚举的属性默认会被忽略
// "{"y": "y"}"
JSON.stringify(
Object.create(
null,
{
x: { value: 'x', enumerable: false },
y: { value: 'y', enumerable: true }
}
)
)

JSON.stringify() 的 replacer 参数

replacer 参数可以是一个函数或者一个数组,作为函数,它有两个参数,键值都会被序列化

  • 如果返回一个数值,转换成相应的字符串被添加入 JSON 字符串
  • 如果返回一个字符串,该字符串作为属性值被添加入 JSON
  • 如果返回一个布尔值,"true" 或者 "false" 被作为属性值被添加入 JSON 字符串
  • 如果返回任何其他对象,该对象递归地序列化成 JSON 字符串,对每个属性调用 replacer 方法(除非该对象是一个函数,这种情况将不会被序列化成 JSON 字符串)
  • 如果返回 undefined,该属性值不会在 JSON 字符串中输出

需要注意的是,不能用 replacer 方法,从数组中移除值(values),如若返回 undefined 或者一个函数,将会被 null 取代

1
2
3
4
5
6
7
8
9
10
11
var foo = { foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7 }

var jsonString = JSON.stringify(foo, (key, value) => {
if (typeof value === 'string') {
return undefined
}
return value
})

// {"week": 45, "month": 7}
console.log(jsonString)

如果 replacer 是一个数组,数组的值代表将被序列化成 JSON 字符串的属性名(即只有包含在这个数组中的属性名才会被序列化)

1
2
3
4
var foo = { foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7 }

// 只保留 week 和 month 属性值
console.log(JSON.stringify(foo, ['week', 'month']))

关于 toJSON 方法

需要注意的是,如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为(即为调用 toJSON 方法后的返回值会被序列化)

1
2
3
4
5
6
7
8
9
var obj = {
foo: 'foo',
toJSON: function () {
return 'bar'
}
}

JSON.stringify(obj) // '"bar"'
JSON.stringify({ x: obj }) // '{"x":"bar"}'

此外 JSON.stringify() 还将会正常序列化 Date 对象,如下所示

1
2
// {"now": "2018-03-01T08:42:34.893Z"}
JSON.stringify({ now: new Date() })

原因是因为 Date 对象内部已经部署了 toJSON() 方法(同 Date.toISOString()),因此 Date 对象会被当做字符串处理

使用 JSON.stringify 来格式化对象

在平常的开发过程中,经常会遇到一些十分复杂的对象,往往是对象当中嵌套对象,看上去十分的不直观,我们可以利用 replacerspace 参数来对其进行格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 因为函数如果出现在非数组对象的属性值中时,在序列化过程中会被忽略,所以需要特殊处理
var foo = function (key, value) {
if (typeof value === 'function' || typeof value === 'symbol') {
return value.toString()
} else if (typeof value === 'undefined') {
return 'undefined'
} else {
return value
}
}

var json = {
bar: 'aaa', foo: { age: Symbol('23') }, baz: undefined,
o: {
name: 'zhangsan',
userInfo: { sex: 0, getSex: function () { return this.sex }, address: null } }
}

JSON.stringify(json, foo, 2)

// {
// "bar": "aaa",
// "foo": {
// "age": "Symbol(23)"
// },
// "baz": "undefined",
// "o": {
// "name": "zhangsan",
// "userInfo": {
// "sex": 0,
// "getSex": "function () { return this.sex }",
// "address": null
// }
// }
// }

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×