探索如何使用 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 的相關知識,才知道這些本來覺得很底層的基礎知識其實對真實業務也有着巨大的做用。夯實基礎,永遠都是很重要的。

相關文章
相關標籤/搜索