JSON序列化

2022-03-15 19:00:16
JSON 序列化即將 JSON 物件處理為 JSON 字串的過程,以方便資料的傳輸。

JSON 序列化可以通過兩種方式來實現,一種是呼叫 JSON 物件內建的 stringify() 函數,一種是為物件自定義 toJSON() 函數。

JSON.stringify() 函數

JSON.stringify() 函數是將一個 JavaScript 物件或者陣列轉換為 JSON 字串,它的基本用法如下所示:

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

其中各個引數含義如下:

  • value 參數列示待處理成 JSON 字串的 JavaScript 值,通常為物件或者陣列。
  • replacer 引數是一個可選引數。如果其值為一個函數,則表示在序列化過程中,被序列化值的每個屬性都會經過該函數的處理;如果其值為一個陣列,則表示只有包含在這個陣列中的屬性名才會被序列化到最終的 JSON 字串中;如果該值為 null 或者未傳遞,則 value 引數對應值的所有屬性都會被序列化。
  • space 是一個可選引數,用於指定縮排用的空白字串,美化輸出。如果引數是個數位,則代表有多少個空格,上限值為 10;如果該引數的值小於 1,則意味著沒有空格;如果引數為字串,則取字串的前十個字元作為空格;如果沒有傳入引數或者傳入的值為 null,將沒有空格。

我們通過以下程式碼來看看 JSON. stringify() 函數的基本使用方法:

首先定義一個待序列化的物件。
var obj = {
    name: 'kingx',
    age: 15,
    address: String('北京市'),
    interest: ['basketball', 'football'],
    email: '[email protected]'
};
console.log(JSON.stringify(obj));
當只傳遞第一個引數時,輸出的結果如下所示:
{"name":"kingx","age":15,"address":" 北京市","interest":["basketball","football"],  "email":"[email protected]"}
當傳遞了 replacer 引數並且值為一個函數時,函數所做的處理是,假如屬性值為字串型別,則將值轉換為大寫。
function replacerFn(key, value) {
    if (typeof value === 'string') {
        return value.toUpperCase();
    }
    return value;
};
console.log(JSON.stringify(obj, replacerFn));
輸出的結果如下所示:
{"name":"KINGX","age":15,"address":"北京市","interest":["BASKETBALL","FOOTBALL"],"email":"[email protected]"}
通過結果可以看出,name、address、email 屬性值為字串型別,其值都轉換成了大寫字母,但是 interest 屬性值為陣列型別,為什麼陣列中的值也轉換成了大寫字母呢?

這就涉及遞迴呼叫的問題,在 JSON 序列化時,如果屬性值為物件或者陣列,則會繼續序列化該屬性值,直到屬性值為基本型別、函數或者 Symbol 型別才結束。

針對上面的範例,obj 物件的 name、address、email 屬性值經過 replacerFn() 函數處理後,會返回大寫的值;age 屬性值為數位型別,不做任何處理,會直接返回值本身;

而 interest 屬性值型別為陣列,return 回來後陣列中的每個值會再次經過 replacerFn() 函數處理,因為陣列中的元素此時都為 string 型別,返回的值會轉換成大寫。

當 replacer 引數為一個陣列時,陣列元素的值代表將要進行序列化成 JSON 字串的屬性名。

如上面的例子,我們呼叫以下函數,並且只序列化 name 屬性和 age 屬性的值。
console.log(JSON.stringify(obj, ['name', 'age']));
輸出的結果如下所示:
{"name":"kingx","age":15}
關於 JSON 序列化,有以下一些注意事項:

  • 非陣列物件的屬性不能保證以特定的順序出現在序列化後的字串中。
  • 布林值、字串、數位的包裝物件在序列化過程中會被轉換為對應的原始值。

以下是一段序列化陣列元素為多種包裝型別的的程式碼。
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
輸出的結果如下所示:
'[1,"false",fal​se]'
  • 在非陣列物件中,undefined、任意的函數及 Symbol 值,在序列化時會被忽略;在陣列物件中,它們會被序列化為 null。
JSON.stringify({x: undefined, y: Object, z: Symbol("")});
在非陣列物件中輸出的結果如下所示:
'{}'
JSON.stringify([undefined, Object, Symbol("")]);
在陣列物件中輸出的結果如下所示:
'[null,null,null]'
  • 對包含迴圈參照物件進行序列化時會丟擲異常。

定義兩個迴圈參照的物件,並呼叫 stringify() 函數輸出結果。
var a = {"name": "z z z"};
var b = {"name": "vvv"};
a.child = b;
b.parent = a;

console.log(JSON.stringify(a));
執行後,控制檯會丟擲異常。提示資訊為迴圈參照結果轉換為 JSON 失敗。
TypeError: Converting circular structure to JSON
  • 所有以 symbol 為屬性鍵的屬性都會被完全忽略掉。
JSON.stringify({[Symbol("foo")]: "foo"});
輸出的結果如下所示:
'{}'
  • 不可列舉的屬性值會被忽略。

建立一個物件,包含一個可列舉的屬性、一個不可列舉的屬性,對其進行序列化。
var p = Object.create(null, {
    name: {
        value: 'xiaoming',
        enumerable: false
    },
        age: {
        value: 15,
        enumerable: true
    }
});
console.log(JSON.stringify(p));
輸出的結果如下所示:
{"age":15}

自定義 toJSON() 函數

如果一個被序列化的物件擁有 toJSON() 函數,那麼 toJSON() 函數就會覆蓋預設的序列化行為,被序列化的值將不再是原來的屬性值,而是 toJSON() 函數的返回值。

toJSON() 函數用於更精確的控制序列化,可以看作是對 stringify() 函數的補充。

我們同樣使用前面例子,定義一個物件,增加 toJSON() 函數。
var obj2 = {
    name: 'kingx',
    age: 15,
    address: String('北京市'),
    interest: ['basketball', 'football'],
    email: '[email protected]',
    toJSON: function () {
          // 只返回name和age屬性值,並且修改key
        return {
            Name: this.name,
            Age: this.age
        };
    }
};
呼叫 JSON.stringify() 函數。
console.log(JSON.stringify(obj 2));
console.log(JSON.stringify({name: obj 2}, ['name']));
輸出的結果如下所示:
{"Name":"kingx","Age":15}
{"name":{}}
對於第一個結果,因為 obj2 有 toJSON() 函數,所以返回值為帶有 Name 和 Age 屬性的值“{"Name":"kingx","Age":15}”,然後直接進行序列化,得到結果。

對於第二個結果,obj2 物件在呼叫 toJSON() 函數後的返回值是“{"Name":"kingx","Age":15}”,實際進行序列化的值為“{name: {"Name":"kingx","Age":15}}”。

此時傳遞了 replacer 引數,因為 replacer 為一個陣列,過濾的是 name 屬性,但是 name 屬性值為一個物件,則需要對物件中的每個屬性遞迴序列化,而“Name”和“Age”與要過濾的屬性“name”值不相等,所以過濾後的值就為一個空物件 {},所以最終結果為“{"name":{}}”。

因此,序列化處理的順序如下:

  • 如果待序列化的物件存在 toJSON() 函數,則優先呼叫 toJSON() 函數,以 toJSON() 函數的返回值作為待序列化的值,否則返回 JSON 物件本身。
  • 如果 stringify() 函數提供了第二個引數 replacer,則對上一步的返回值經過 replacer 引數處理。
  • 如果 stringify() 函數提供了第三個引數,則對 JSON 字串進行格式化處理,返回最終的結果。