探索如何使用 JSON.stringify() 去序列化一个 Error

image

最近在作 Node 服务端需求的时候,遇到了几回服务端报错的问题。打 log 发现均是一些 Error,可是它们都无法很好地透传给前端浏览器,出现问题只能查看服务端机器的日志,调试起来很是不方便。思考了一下,服务端的内容都是经过 JSON.stringify() 处理,而后设置 Content-type: text/json 的响应头之后再传给前端的,若是 Error 也可以被这样处理,那么调试起来就方便多了。javascript

举个例子

说到 JSON.stringify() 这个方法,相信全部玩过 JS 的同窗都不会陌生。它可以方便地把一个对象转化成字符串,在不一样的场景中都有着极大的用处。可是它也有一个较大的缺点,没法直接处理诸如 Error 一类的对象。前端

首先来看个例子:java

const err = new Error('This is an error')
JSON.stringify(err)

// => "{}"

在控制台运行上述代码后会发现,JSON.stringify() 的结果是一个字符串的 "{}",里面没有任何有效内容。这是否意味着 JSON.stringify() 确实没法处理 Error 呢?下面咱们来看看在 MDN 里这个函数是如何定义的。json

MDN 定义

首先来看看描述数组

JSON.stringify()将值转换为相应的JSON格式:浏览器

  • 转换值若是有toJSON()方法,该方法定义什么值将被序列化。
  • 非数组对象的属性不能保证以特定的顺序出如今序列化后的字符串中。
  • 布尔值、数字、字符串的包装对象在序列化过程当中会自动转换成对应的原始值。
  • undefined、任意的函数以及 symbol 值,在序列化过程当中会被忽略(出如今非数组对象的属性值中时)或者被转换成 null(出如今数组中时)。函数、undefined被单独转换时,会返回undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined).
  • 对包含循环引用的对象(对象之间相互引用,造成无限循环)执行此方法,会抛出错误。
  • 全部以 symbol 为属性键的属性都会被彻底忽略掉,即使 replacer 参数中强制指定包含了它们。
  • Date日期调用了toJSON()将其转换为了string字符串(同Date.toISOString()),所以会被当作字符串处理。
  • NaN和Infinity格式的数值及null都会被当作null。
  • 其余类型的对象,包括Map/Set/weakMap/weakSet,仅会序列化可枚举的属性。

列了那么多实际上是为了凑字数咱们只看最后一条描述:函数

其余类型的对象,包括Map/Set/weakMap/weakSet,仅会序列化可枚举的属性。

“仅会序列化可枚举的属性”,是什么意思呢?众所周知,在 JS 的世界中一切皆对象,对象有着不一样的属性,这些属性是否可枚举,咱们用 enumerable 来定义。spa

对象属性的 enumerable

举个例子,咱们用 obj = { a: 1, b: 2, c: 3 } 来定义一个对象,而后设置它的 c 属性为“不可枚举”,看看效果会如何:调试

首先看处理前的效果:日志

const obj = { a: 1, b: 2, c: 3 }
JSON.stringify(obj)

// => "{"a":1,"b":2,"c":3}"

再看处理后的效果:

const obj = { a: 1, b: 2, c: 3 }

Object.defineProperty(obj, 'c', {
  value: 3,
  enumerable: false
})

JSON.stringify(obj)

// => "{"a":1,"b":2}"

能够看到,在对 c 属性设置为不可枚举之后,JSON.stringify() 便再也不对其进行序列化。

咱们把问题再深刻一些,有没有办法可以获取一个对象中包含不可枚举在内的全部属性呢?答案是使用 Object.getOwnPropertyNames() 方法。

依然是刚刚被改装过的 obj 对象,咱们来看看它所包含的全部属性:

Object.getOwnPropertyNames(obj)

// => ["a", "b", "c"]

不可枚举的 c 属性也被获取到了!

用一样的方法,咱们来看看一个 Error 都包含哪些属性:

const err = new Error('This is an error')
Object.getOwnPropertyNames(err)

// => ["stack", "message"]

能够看到,Error 包含了 stackmessage 两个属性,它们都可以使用点运算符 .err 实例里面拿到。

既然咱们已经可以获取 Error 实例的不可枚举属性及其内容,那么距离使用 JSON.stringify() 序列化 Error 也已经不远了!

JSON.stringify() 的第二个参数

JSON.stringify() 能够接收三个参数:

语法

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

value

将要序列化成 一个JSON 字符串的值。

replacer 可选

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

space 可选

指定缩进用的空白字符串,用于美化输出(pretty-print);若是参数是个数字,它表明有多少的空格;上限为10。该值若小于1,则意味着没有空格;若是该参数为字符串(字符串的前十个字母),该字符串将被做为空格;若是该参数没有提供(或者为null)将没有空格。
返回值 节
一个表示给定值的JSON字符串。

咱们来看 replacer 的用法:

……若是该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中……

依然使用上文的 obj 为例子:

const obj = { a: 1, b: 2, c: 3 }

Object.defineProperty(obj, 'c', {
  value: 3,
  enumerable: false
})

JSON.stringify(obj, ['a', 'c'])

// => "{"a":1,"c":3}"

能够看到,咱们在 replacer 中指定了要序列化 ac 属性,输出结果也是只有这两个属性的值,且不可枚举的 c 属性也被序列化了!守得云开见月明,Error 对象被序列化的方法也就出来了:

const err = new Error('This is an error')

JSON.stringify(err, Object.getOwnPropertyNames(err), 2)

// => 
// "{
//   "stack": "Error: This is an error\n    at <anonymous>:1:13",
//   "message": "This is an error"
// }"

后记

文章原本的标题是“你不知道的 JSON.stringify()”,可是总感受词不达意,有标题党的嫌疑,遂改为更为实在的现标题。

对于一些经常使用的函数,其背后也有着许多值得探索的内容,好比此次为了让 JSON.stringify() 去序列化一个 Error,我又复习了一遍 JS 对象属性中 enumerable 的相关知识,才知道这些本来觉得很底层的基础知识其实对真实业务也有着巨大的做用。夯实基础,永远都是很重要的。

相关文章
相关标签/搜索