再谈 JSON, 你可能不知道的一些 JSON 用法及误区
最近也是看到很多人在讨论 JSON
, 但有些点说得不太准确,翻翻犀牛书,再次复习一下。
JSON.stringify(value [, replacer [, space]])
第一个参数一般是对象(Object)或数组(Array),或 javascript
基本值。
误区:不支持的值都会被忽略
首先 JSON 只能序列化可枚举的值,其次 undefined
,NaN
,Infinity
,function
, Date
, RegExp
, Error
这些值或对象都是不支持的,但并不是这些值都会被忽略。
var o = {
a: Symbol(), // 新增的 ES6 基本值
b: [1,2,3],
c: undefined,
d: NaN,
e: Infinity,
f: function (){ return 1;},
g: new Date(),
h: /^\d$/gi,
i: new Error('err'),
j: {}
}
JSON.stringify(o);
// output: '{"b":[1,2,3],"d":null,"e":null,"g":"2017-07-28T02:56:37.102Z","h":{},"i":{},"j":{}}'
由上可以看出:
Symbol
,undefined
,function
会直接被忽略NaN
,Infinity
被转换成了null
RegExp
,Error
则被转成了空对象 {}
replacer
因为 JSON.stringify()
序列化不支持部分值,所以不能简单地使用 JSON
的两个方法进行深拷贝。但可以利用 JSON.stringify()
的第二个参数进行正确序列化。
但这并不推荐,因为 JSON.parse() 仍然无法还原不被支持的值。只有对象字面量 Object 和 数组 Array 可以被正确还原,其他都变成字符串
// 沿用上一例子的代码
var replacer = function(k ,v) {
var type = typeof v;
if(type === 'function') {
return Function.prototype.toString.call(v)
}
if (type === 'undefined'){
return 'undefined'
}
if (type === 'number'){
if (v !== v){
return 'NaN'
}
if (v === Infinity){
return 'Infinity'
}
}
if (type === 'symbol'){
return Symbol.prototype.toString.call(v)
}
if (v instanceof RegExp){
return RegExp.prototype.toString.call(v)
}
if (v instanceof Error && v.name !== '' && v.message !== ''){
return 'new ' + v.name + '('+ v.message +')'
}
return v
}
JSON.stringify(o, replacer);
// output: '{"a":"Symbol()","b":[1,2,3],"c":"undefined","d":"NaN","e":"Infinity","f":"function (){ return 1;}","g":"2017-07-28T02:56:37.102Z","h":"/^\\d$/gi","i":"new Error(err)","j":{}}'
另外一个用法就是传入数组,指定需要序列化的key.这个在提取部分属性的时候也是很有用。
// 沿用上面的代码
JSON.stringify(o, ["b", "c", "d"]);
// output: '{"b":[1,2,3],"d":null}'
space
是一个可选的美化属性,序列化时是没有换行和空格的,此参数可指定一个数值(大于10则限制为10)或一组字符(长度最长为10个字符), 此参数不为空会自动插入换行。
但是设置为字符时,无法使用 parse 复原
JSON.stringify(o, null, '-');
// '{
// -"b": [
// --1,
// --2,
// --3
// -],
// -"d": null,
// -"e": null,
// -"g": "2017-07-28T02:56:37.102Z",
// -"h": {},
// -"i": {},
// -"j": {}
// }'
JSON.parse(value[, reviver])
JSON.parse
与 JSON.stringify
操作正好相反,但 value
必须严格符合 JSON
格式,否则报错。
第二个参数可传入一个函数,用以转换解析出来的值。
比如 Date
对象在序列化之后无法逆转成一个 Date
对象,此时可写一个函数去转换:
var reviver = function (name, value){
console.log(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ$/.test(value));
if (typeof value === 'string' && /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ$/.test(value)){
return new Date(value)
}
return value
}
// 沿用前个例子的代码
JSON.parse(JSON.stringify(o.g), reviver)
// output: Fri Jul 28 2017 10:56:37 GMT+0800 (+08)
toJSON()
有些文章会说 Object.toJSON()
,这是不对的,Object
原型上并没有定义 toJSON
方法,该方法只被定义在 Date
对象上。JSON.stringify()
在序列化的时候会首先查找该方法,如果有则直接返回 toJSON
的调用结果。这个一开始是让 Date
正确序列化成 JSON
时设计的。但我们也可以利用 toJSON
的屏蔽功能。
Function.prototype.toJSON = function (){
return Function.prototype.toString.call(this)
}
// 沿用上面的例子
JSON.stringify(o);
// {"b":[1,2,3],"d":null,"e":null,"f":"function (){ return 1;}","g":"2017-07-28T02:56:37.102Z","h":{},"i":{},"j":{}}
会发现,function
没有被忽略,但此方法要慎用,在原型上添加方法可能产生副作用。