JSON序列化和反序列化
1、JSON序列化
JSON序列化即将JSON对象处理为JSON字符串的过程,以方便数据的传输。
JSON序列化可以通过两种方式来实现,一种是调用JSON对象内置的stringify()函数,一种是为对象自定义toJSON()函数。
1.1、JSON.stringify()函数
JSON.stringify()函数是将一个JavaScript对象或者数组转换为JSON字符串,它的基本用法如下所示。
js
JSON.stringify(value[, replacer [, space]])
- value参数表示待处理成JSON字符串的JavaScript值,通常为对象或者数组。
- replacer参数是一个可选参数。如果其值为一个函数,则表示在序列化过程中,被序列化值的每个属性都会经过该函数的处理;如果其值为一个数组,则表示只有包含在这个数组中的属性名才会被序列化到最终的JSON字符串中;如果该值为null或者未传递,则value参数对应值的所有属性都会被序列化。
- space是一个可选参数,用于指定缩进用的空白字符串,美化输出。如果参数是个数字,则代表有多少个空格,上限值为10;如果该参数的值小于1,则意味着没有空格;如果参数为字符串,则取字符串的前十个字符作为空格;如果没有传入参数或者传入的值为null,将没有空格。
通过以下代码来看看JSON. stringify()函数的基本使用方法。首先定义一个待序列化的对象。
js
var obj = {
name: 'kingx',
age: 15,
address: String('北京市'),
interest: ['basketball', 'football'],
email: 'zhouxiongking@163.com'
};
console.log(JSON.stringify(obj));
当只传递第一个参数时,输出的结果如下所示:
js
{"name":"kingx","age":15,"address":" 北京市","interest":["basketball","football"], "email":"zhouxiongking@163.com"}
当传递了replacer参数并且值为一个函数时,函数所做的处理是,假如属性值为字符串类型,则将值转换为大写。
js
function replacerFn(key, value) {
if(typeof value === 'string') {
return value.toUpperCase();
}
return value;
}
console.log(JSON.stringify(obj, replacerFn));
输出的结果如下所示:
js
{"name":"KINGX","age":15,"address":"北京市","interest":["BASKETBALL","FOOTBALL"],"email":"ZHOUXIONGKING@163.COM"}
通过结果可以看出,name、address、email属性值为字符串类型,其值都转换成了大写字母,但是interest属性值为数组类型,为什么数组中的值也转换成了大写字母呢?
这就涉及递归调用的问题,在JSON序列化时,如果属性值为对象或者数组,则会继续序列化该属性值,直到属性值为基本类型、函数或者Symbol类型才结束。
针对上面的实例,obj对象的name、address、email属性值经过replacerFn()函数处理后,会返回大写的值;age属性值为数字类型,不做任何处理,会直接返回值本身;而interest属性值类型为数组,return回来后数组中的每个值会再次经过replacerFn()函数处理,因为数组中的元素此时都为string类型,返回的值会转换成大写。
当replacer参数为一个数组时,数组元素的值代表将要进行序列化成JSON字符串的属性名。
如上面的例子,我们调用以下函数,并且只序列化name属性和age属性的值。
js
console.log(JSON.stringify(obj, ['name', 'age']));
输出的结果如下所示。
js
{"name":"kingx","age":15}
关于JSON序列化,有以下一些注意事项。
- 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
- 布尔值、字符串、数字的包装对象在序列化过程中会被转换为对应的原始值。
以下是一段序列化数组元素为多种包装类型的的代码。
js
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
输出的结果如下所示:
js
'[1,"false",false]'
- 在非数组对象中,undefined、任意的函数及Symbol值,在序列化时会被忽略;在数组对象中,它们会被序列化为null。
js
JSON.stringify({x: undefined, y: Object, z: Symbol("")});
在非数组对象中输出的结果如下所示。
js
'{}'
JSON.stringify([undefined, Object, Symbol("")]);
在数组对象中输出的结果如下所示。
js
'[null,null,null]'
- 对包含循环引用对象进行序列化时会抛出异常。
定义两个循环引用的对象,并调用stringify()函数输出结果。
js
var a = {"name": "z z z"};
var b = {"name": "vvv"};
a.child = b;
b.parent = a;
console.log(JSON.stringify(a));
运行后,控制台会抛出异常。提示信息为循环引用结果转换为JSON失败。
js
TypeError: Converting circular structure to JSON
- 所有以symbol为属性键的属性都会被完全忽略掉。
js
JSON.stringify({[Symbol("foo")]: "foo"});
输出的结果如下所示。
js
'{}'
- 不可枚举的属性值会被忽略。
创建一个对象,包含一个可枚举的属性、一个不可枚举的属性,对其进行序列化。
js
var p = Object.create(null, {
name: {
value: 'xiaoming',
enumerable: false
},
age: {
value: 15,
enumerable: true
}
});
console.log(JSON.stringify(p));
输出的结果如下所示。
js
{"age":15}
2.2、自定义toJSON()函数
如果一个被序列化的对象拥有toJSON()函数,那么toJSON()函数就会覆盖默认的序列化行为,被序列化的值将不再是原来的属性值,而是toJSON()函数的返回值。
toJSON()函数用于更精确的控制序列化,可以看作是对stringify()函数的补充。
我们同样使用前面例子,定义一个对象,增加toJSON()函数。
js
var obj2 = {
name: 'kingx',
age: 15,
address: String('北京市'),
interest: ['basketball', 'football'],
email: 'zhouxiongking@163.com',
toJSON: function () {
// 只返回name和age属性值,并且修改key
return {
Name: this.name,
Age: this.age
};
}
};
调用JSON.stringify()函数。
java
console.log(JSON.stringify(obj 2));
console.log(JSON.stringify({name: obj 2}, ['name']));
输出的结果如下所示。
js
{"Name":"kingx","Age":15}
{"name":{}}
对于第一个结果,因为obj2有toJSON()函数,所以返回值为带有Name和Age属性的值"{"Name":"kingx","Age":15}",然后直接进行序列化,得到结果。
对于第二个结果,obj2对象在调用toJSON()函数后的返回值是"{"Name":"kingx","Age":15}",实际进行序列化的值为"{name:{"Name":"kingx","Age":15}}"。此时传递了replacer参数,因为replacer为一个数组,过滤的是name属性,但是name属性值为一个对象,则需要对对象中的每个属性递归序列化,而"Name"和"Age"与要过滤的属性"name"值不相等,所以过滤后的值就为一个空对象{},所以最终结果为"{"name":{}}"。
因此,序列化处理的顺序如下。
- 如果待序列化的对象存在toJSON()函数,则优先调用toJSON()函数,以toJSON()函数的返回值作为待序列化的值,否则返回JSON对象本身。
- 如果stringify()函数提供了第二个参数replacer,则对上一步的返回值经过replacer参数处理。
- 如果stringify()函数提供了第三个参数,则对JSON字符串进行格式化处理,返回最终的结果。
2、JSON反序列化
JSON反序列化即将JSON字符串转换为JSON对象的过程,得到的结果用于在JavaScript中做逻辑处理。
JSON反序列化的实现方式有两种,一种是使用JSON对象内置的parse()函数,一种是使用eval()函数。
2.1、JSON.parse()函数
JSON.parse()函数用来解析JSON字符串,构造由字符串描述的JavaScript值或对象,它的语法如下所示。
js
JSON.parse(text[, reviver])
- text表示待解析的JSON字符串。
- reviver是一个可选参数。如果是一个函数,则规定了原始值在返回之前如何被解析改造。如果被解析的JSON字符串是非法的,则会抛出异常。
我们首先来看看JSON.parse()函数的基本使用方法,包括对数组、对象、基本数据类型的处理。
js
JSON.parse('[1,2,3,true]'); // Array [1, 2, 3, true]
JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
JSON.parse('true'); // true
JSON.parse('123.45'); // 123.45
JSON.parse()函数还可以接收一个函数,用来处理JSON字符串中的每个属性值。当属性值为一个数组或者对象时,数组中的每个元素或者对象的每个属性都会经过reviver参数对应的函数处理。执行的顺序是从最内层开始,按照层级顺序,依次向外遍历。
我们通过以下这段代码看看parse()函数传递reviver参数的处理情况。
首先定义JSON字符串,然后调用JSON.parse()函数进行解析。
js
var jsonStr = '{"name":"kingx","age":15,"address":"北京市","interest":["basketball",
"football"],"children":[{"name":"kingx2","age":20}],"email":"zhouxgking@163.com"}';
var result = JSON.parse(jsonStr, function (key, value) {
if (key === 'name') {
return value + '同学';
}
if (key === 'age') {
return value * 2;
}
return value;
});
console.log(result);
js
{
name: 'kingx同学',
age: 30,
address: '北京市',
interest: ['basketball', 'football'],
children: [{
name: 'kingx2同学',
age: 40
}],
email: 'zhouxiongking@163.com'
}
通过结果可以看出,解析后的name属性的值都加上了"同学",age属性的值都乘以了2。当使用JSON.parse()函数解析JSON字符串时,需要注意两点。
- JSON字符串中的属性名必须用双引号括起来,否则会解析错误。
以下是一些正确和错误的写法。
js
var json = '{"name":"kingx"}'; // 这个是正确的JSON格式
var json = "{\"name\":\"kingx\"}"; // 这个也是正确的JSON格式
var json = '{name:"kingx"}'; // 这个是错误的JSON格式,因为属性名没有用双引号括起来
var json = "{'name':'kingx'}"; //这个也是错误的JSON格式,属性名应该用双引号括起来,而它用了单引号
- JSON字符串不能以逗号结尾,否则会解析异常。
js
JSON.parse("[1, 2, 3, 4, ]"); // 解析异常,数组最后一个元素后面出现逗号
JSON.parse('{"foo" : 1, }'); // 解析异常,最后一个属性值后面出现逗号
2.2、eval()函数
eval()函数用于计算JavaScript字符串,并把它作为脚本来执行。在使用eval()函数进行JSON反序列化时,其语法如下所示。
js
eval("(" + str + ")")
其中,str表示待处理的字符串。
这里为什么要使用括号将拼接出来的字符串括起来呢?
因为JSON字符串是以"{}"开始和结束的,在JavaScript中它会被当作一个语句块来处理,所以必须强制将它处理成一个表达式,所以采用括号。
js
var json1 = '{"name":"kingx"}';
var json2 = '{"address":["beijing","shanghai"]}';
console.log(eval("(" + json1 + ")"));// {name: "kingx"}
console.log(eval("(" + json2 + ")"));// {address: ["beijing", "shanghai"]}