你可能不知道的JSON用法及误区

再谈 JSON, 你可能不知道的一些 JSON 用法及误区

最近也是看到很多人在讨论 JSON, 但有些点说得不太准确,翻翻犀牛书,再次复习一下。

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

第一个参数一般是对象(Object)或数组(Array),或 javascript 基本值。

误区:不支持的值都会被忽略

首先 JSON 只能序列化可枚举的值,其次 undefinedNaNInfinityfunction, 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.parseJSON.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 没有被忽略,但此方法要慎用,在原型上添加方法可能产生副作用。

0%