你不知道的 JSON.stringify

前端小智發表於2021-12-28
作者:BlackLivesMatter
譯者:前端小智
來源:devinduct
有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

JSON.stringify是我們經常用到的的一個方法,它主要作用是將 JavaScript 值和物件轉換為字串。如:

JSON.stringify({ foo: "bar" });
// => '{"foo":"bar"}'

JSON.stringify(123);
// => '123'

但是JS 的許多地方都有問題,這個函式也不例外。我們可能會想象一個叫做 "stringify "的函式總是返回一個字串......但它並沒有!

例如,如果你嘗試 stringify undefined,它返回 undefined ,而不是一個字串。

JSON.stringify(undefined);
// => undefined

接下來,我將分兩部分講:

  • 列舉 JSON.stringify 不返回字串的情況
  • 我們將如何避免這些陷阱

什麼時候 JSON.stringify 不返回字串?

undefined、任意的函式以及 symbol 值,在序列化過程中會被忽略(出現在非陣列物件的屬性值中時)或者被轉換成 null(出現在陣列中時)。函式、undefined 被單獨轉換時,會返回 undefined

對包含迴圈引用的物件(物件之間相互引用,形成無限迴圈)執行此方法,會丟擲錯誤

我認為 JSON.stringify 能夠返回字串以外的東西是挺驚訝的。但在6種情況下,它可以返回undefined:

  1. 試圖在頂層對 undefined 進行序列化,會返回 undefined
JSON.stringify(undefined);
// => undefined
  1. 嘗試序列化函式也會返回 undefined。對於常規函式、箭頭函式、非同步函式和生成器函式都是如此。
JSON.stringify(function foo() {});
// => undefined

JSON.stringify(() => {});
// => undefined

function bar() {}
bar.someProperty = 123;
JSON.stringify(bar);
// => undefined
  1. 嘗試序列化symbol 也會返回 undefined
JSON.stringify(Symbol("computers were a mistake"));
// => undefined
  1. 在瀏覽器中,試圖序列化被廢棄的 document.all 也會返回 undefined
// => undefined

這隻影響到瀏覽器,因為document.all在其他環境中是不可用的,比如Node。

  1. 帶有 toJSON 函式的物件將被執行,而不是試圖正常地序列化它們。但是如果 toJSON 返回上面的一個值,試圖在頂層序列化它將導致 JSON.stringify 返回undefined
JSON.stringify({ toJSON: () => undefined });
// => undefined

JSON.stringify({ ignored: true, toJSON: () => undefined });
// => undefined

JSON.stringify({ toJSON: () => Symbol("heya") });
// => undefined
  1. 你可以傳遞第二個引數,稱為 "replacer",它可以改變序列化的邏輯。如果這個函式為頂層返回上述值之一,JSON.stringify 將返回undefined
JSON.stringify({ ignored: true }, () => undefined);
// => undefined

JSON.stringify(["ignored"], () => Symbol("hello"));
// => undefined

需要注意的是,其中的許多東西實際上隻影響到頂層的序列化。例如,JSON.stringify({foo: undefined}),返回字串"{}",這並不令人驚訝。

我還想提一下,TypeScript的型別定義在這裡是不正確的。例如,下面的程式碼型別的校驗可以通過:

const result: string = JSON.stringify(undefined);

在第2部分中,我們將討論如何更新 TypeScript 的定義以確保其正確性。

JSON.stringify 也可能遇到問題,導致它丟擲一個錯誤。在正常情況下,有四種情況會發生:

  1. 迴圈引用會導致丟擲一個型別錯誤。
const b = { a };
a.b = b;

JSON.stringify(a);
// => TypeError: cyclic object value

注意,這些錯誤訊息在不同瀏覽器可能提示是不樣的,例如,Firefox 的錯誤資訊與Chrome的不同。

  1. BigInts不能用 JSON.stringify 進行序列化,這些也會導致一個TypeError。
JSON.stringify(12345678987654321n);
// => TypeError: BigInt value can't be serialized in JSON

JSON.stringify({ foo: 456n });
// => TypeError: BigInt value can't be serialized in JSON
  1. 帶有 toJSON 函式的物件將被執行。如果這些函式丟擲錯誤,它將冒泡到呼叫者。
const obj = {
  foo: "ignored",
  toJSON() {
    throw new Error("Oh no!");
  },
};

JSON.stringify(obj);
// => Error: Oh no!
  1. 你可以傳遞第二個引數,稱為 replacer。如果這個函式丟擲一個錯誤,它將冒泡。
JSON.stringify({}, () => {
  throw new Error("Uh oh!");
});
// => Error: Uh oh!

現在我們已經看到了 JSON.stringify 不返回字串的情況,接下來,我們來看看如何避免這些問題。

如何避免這些問題

沒有關於如何解決這些缺陷的通用方法,所以這裡只介紹一些常見的情況。

處理迴圈引用

根據個人經驗,JSON.stringify 在傳遞迴圈引用時最容易出錯。如果這對你來說是一個常見的問題,我推薦 json-stringify-safe 包,它能很好地處理這種情況。

const stringifySafe = require("json-stringify-safe");

const a = {};
const b = { a };
a.b = b;

JSON.stringify(a);
// => TypeError: cyclic object value

stringifySafe(a);
// => '{"b":{"a":"[Circular ~]"}}'

封裝

你可能想用你自己的自定義函式來封裝 JSON.stringify。你可以決定你想要它做什麼。錯誤應該冒出來嗎?如果 JSON.stringify 返回 undefined,應該怎麼做?

例如,Signal Desktop有一個名為 reallyJsonStringify 的函式,它總是返回一個用於除錯的字串。就像這樣

function reallyJsonStringify(value) {
  let result;
  try {
    result = JSON.stringify(value);
  } catch (_err) {
    // If there's any error, treat it like `undefined`.
    result = undefined;
  }

  if (typeof result === "string") {
    // It's a string, so we're good.
    return result;
  } else {
    // Convert it to a string.
    return Object.prototype.toString.call(value);
  }
}

關於TypeScript型別的說明

如果你已經在用 TypeScript,可能會驚訝地發現,TypeScript對 JSON.stringify的官方定義在這裡並不正確。它們實際上看起來像這樣:

// Note: 這裡面簡化過
interface JSON {
  // ...
  stringify(value: any): string;
}

不幸的是,這是一個長期存在的問題,沒有一個完美的解決方案。

你可以嘗試修補 JSON.stringify 的型別,但每個解決方案都有一定的缺點。我建議用自定義型別定義自己的包裝器並。例如,Signal Desktop的reallyJsonStringify 的模板:

function reallyJsonStringify(value: unknown): string {
  // ...

總結

  • JSON.stringify 有時會返回 undefined,而不是一個字串
  • JSON.stringify 有時會丟擲一個錯誤
  • 我們可以通過用不同的方式包裝函式來解決這個問題

希望這篇文章能讓你對 JSON.stringify 有更全面的瞭解。

我是刷碗智,勵志退休後要回家擺地攤的人,我們下期見。


程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

原文:https://evanhahn.com/when-str...

交流

有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。

本文 GitHub https://github.com/qq44924588... 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

相關文章