你不知道的 JSON.stringify!!!
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:
試圖在頂層對 undefined 進行序列化,會返回 undefined。
- JSON.stringify(undefined);
- // => undefined
嘗試序列化函數也會返回 undefined。對于常規函數、箭頭函數、異步函數和生成器函數都是如此。
- JSON.stringify(function foo() {});
- // => undefined
- JSON.stringify(() => {});
- // => undefined
- function bar() {}
- bar.someProperty = 123;
- JSON.stringify(bar);
- // => undefined
嘗試序列化symbol 也會返回 undefined。
- JSON.stringify(Symbol("computers were a mistake"));
- // => undefined
在瀏覽器中,試圖序列化被廢棄的document.all 也會返回 undefined。
- // => undefined
這只影響到瀏覽器,因為document.all在其他環境中是不可用的,比如Node。
帶有 toJSON 函數的對象將被運行,而不是試圖正常地序列化它們。但是如果 toJSON 返回上面的一個值,試圖在頂層序列化它將導致 JSON.stringify 返回undefined。
- JSON.stringify({ toJSON: () => undefined });
- // => undefined
- JSON.stringify({ ignored: true, toJSON: () => undefined });
- // => undefined
- JSON.stringify({ toJSON: () => Symbol("heya") });
- // => undefined
你可以傳遞第二個參數,稱為 "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 也可能遇到問題,導致它拋出一個錯誤。在正常情況下,有四種情況會發生:
循環引用會導致拋出一個類型錯誤。
- const b = { a };
- a.b = b;
- JSON.stringify(a);
- // => TypeError: cyclic object value
注意,這些錯誤消息在不同瀏覽器可能提示是不樣的,例如,Firefox 的錯誤信息與Chrome的不同。
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
帶有 toJSON 函數的對象將被運行。如果這些函數拋出錯誤,它將冒泡到調用者。
- const obj = {
- foo: "ignored",
- toJSON() {
- throw new Error("Oh no!");
- },
- };
- JSON.stringify(obj);
- // => Error: Oh no!
你可以傳遞第二個參數,稱為 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 有更全面的了解。
作者:BlackLivesMatter 譯者:前端小智
來源:devinduct 原文:https://evanhahn.com/when-stringify-doesnt-return-a-string