浅谈JSON.stringify

JSON不是一种编程语言,而是一种独立于语言的数据交换格式。它获得了当今大部分语言的支持,从而可以在不同平台间进行数据交换。
JSON可以表示数字、布尔值、字符串、null、数组(值的有序序列),以及由这些值(或数组、对象)所组成的对象(字符串与值的映射)。

认识JSON.stringify

在JavaScript中,操作json格式的数据主要通过JSON对象,JSON.stringify是其中一个将JS对象序列化的函数。下面看几段代码

代码清单1

1
2
3
4
5
6
7
8
var json = {
name: "xingmu",
age: 23,
phone: ["huawei", "xiaomi", "sanxing"],
getJob(){ return "coding";}
}
console.log(JSON.stringify(json));
// {"name":"xingmu","age":23,"phone":["huawei","xiaomi","sanxing"]}

上面代码中的函数getJob在序列化过程中被忽略了。

代码清单2

1
2
3
4
5
6
7
8
var json = {
name: "xingmu",
age: 23,
phone: ["huawei", "xiaomi", "sanxing"],
job: undefined
};
console.log(JSON.stringify(json));
// {"name":"xingmu","age":23,"phone":["huawei","xiaomi","sanxing"]}

上面代码中的job值为undefined在序列化过程中被忽略了。

代码清单3

1
2
3
4
5
6
7
8
var json = {
name: "xingmu",
age: 23,
phone: ["huawei", "xiaomi", "sanxing"],
job: Symbol()
};
console.log(JSON.stringify(json));
// {"name":"xingmu","age":23,"phone":["huawei","xiaomi","sanxing"]}

上面代码中的job值为Symbol类型在序列化过程中被忽略了。

代码清单4

1
2
3
4
5
6
7
8
var json = {
name: "xingmu",
age: 23,
phone: ["huawei", "xiaomi", "sanxing"],
[Symbol()]: "coding"
};
console.log(JSON.stringify(json));
// {"name":"xingmu","age":23,"phone":["huawei","xiaomi","sanxing"]}

上面代码中的key为Symbol类型在序列化过程中被忽略了。

代码清单5

1
2
3
4
5
6
7
8
var json = {
name: "xingmu",
age: 23,
phone: ["huawei", function(){}, undefined, Symbol()],
job: "coding"
};
console.log(JSON.stringify(json));
// {"name":"xingmu","age":23,"phone":["huawei",null,null,null],"job":"coding"}

上面的代码中数组phone中的函数、undefined在转换后,变成了null。

代码清单6

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
var notEnum = Symbol("不可枚举symbol");
var proto = {
[Symbol("可枚举symbol")]: "可枚举symbol",
name: "可枚举属性"
};
// 不可枚举属性
Object.defineProperty(proto, "age", {
value: 234
});
// 不可枚举symbol属性
Object.defineProperty(proto, notEnum, {
value: "不可枚举symbol"
});
var json = {
name: "xingmu",
age: 23,
job: "coding"
};
// 继承
Object.setPrototypeOf(json, proto);
// 不可枚举属性
Object.defineProperty(json, "address", {
value: "sh"
});
//可枚举
Object.defineProperty(json, "city", {
enumerable:true,
value: "ks"
});

console.log(JSON.stringify(json));
// {"name":"xingmu","age":23,"job":"coding","city":"ks"}

上面代码中的继承属性和不可枚举属性在序列化过程中被忽略了。

从上面的几段代码中发现,函数/Symbol/undefined在序列化的过程中被忽略了,而当他们出现在数组中的时候,又被转换为了null。继承属性和不可枚举属性在序列化过程中也被忽略了,这是为什么呢?

因为JSON是一个通用的文本格式,和语言无关。设想如果将函数定义也可以被序列化的话,接收数据一方没有对应的格式,无法通过合适的方式将其呈现出来,要完成这个过程将极为复杂。所以和语言特别相关的一些特性在序列化的过程中会被忽略,比如函数/Symbol/undefined这些JavaScript中特有的类型

文档里也提到JavaScript对象在进行序列化过程中需要注意五个方面的问题:

  • 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
  • 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
  • undefined、任意的函数以及 symbol 值,出现在非数组对象的属性值中时,在序列化过程中会被忽略;出现在数组中时,被转换成 null。
  • 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
  • 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
  • 继承属性和不可枚举的属性会被忽略

对象的toJSON函数

上一节我们聊到有些数据类型是无法参与序列化,有没有什么办法可以解决这个问题呢,那就要用到toJSON了。

MDN文档里是这么介绍toJSON的:如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为,这时就不是该对象被序列化,而是调用 toJSON 方法后的返回值参与被序列化。

MDN的一个例子,代码清单7

1
2
3
4
5
6
7
var obj = {
foo: 'foo',
toJSON: function () {
return 'bar';
}
};
JSON.stringify(obj); // '"bar"'

有没有很神奇,下面我们在代码清单1的基础上增加toJSON,看能做点什么

代码清单8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var json = {
name: "xingmu",
age: 23,
phone: ["huawei", "xiaomi", "sanxing"],
getJob() { return "coding"; },
toJSON() {
let { name } = json;
let job = json.getJob();
return {
name, job
};
}

};
console.log(JSON.stringify(json));
// {"name":"xingmu","job":"coding"}

很酷,有没有,我们可以在toJSON中任意定义可以参与序列化的属性,如果在构造函数中使用,将极大的增加复用性,让代码看起来更加优雅简介。

stringify的可选参数

JSON.stringify的语法是这样定义的JSON.stringify(value[, replacer [, space]])

replacer:

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

代码清单9

1
2
3
4
5
6
7
8
9
10
11
12
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}

var foo = { foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7 };
console.log(JSON.stringify(foo, replacer));
// {"week":45,"month":7}.
console.log(JSON.stringify(foo, ['week', 'month']));
// '{"week":45,"month":7}', 只保留“week”和“month”属性值。

space:指定缩进用的空白字符串,用于美化输出(pretty-print)

  • 如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;
  • 如果该参数为字符串(字符串的前十个字母),该字符串将被作为空格;
  • 如果该参数没有提供(或者为null)将没有空格。

请参考文档示例

扩展

序列化概念

对象的寿命通常随着生成该对象的程序的终止而终止。有时候,可能需要将对象的状态保存下来,在需要时再将对象恢复。我们把对象的这种能记录自己的状态以便将来再生的能力。叫作对象的持续性(persistence)。对象通过写出描述自己状态的数值来记录自己 ,这个过程叫对象的序列化(Serialization) 。序列化的主要任务是写出对象实例变量的数值。如果交量是另一对象的引用,则引用的对象也要序列化。

序列化又叫串行化。