Javascript JSON 方法,toJSON

2023-02-17 10:49 更新

假設(shè)我們有一個(gè)復(fù)雜的對(duì)象,我們希望將其轉(zhuǎn)換為字符串,以通過網(wǎng)絡(luò)發(fā)送,或者只是為了在日志中輸出它。

當(dāng)然,這樣的字符串應(yīng)該包含所有重要的屬性。

我們可以像這樣實(shí)現(xiàn)轉(zhuǎn)換:

let user = {
  name: "John",
  age: 30,

  toString() {
    return `{name: "${this.name}", age: ${this.age}}`;
  }
};

alert(user); // {name: "John", age: 30}

……但在開發(fā)過程中,會(huì)新增一些屬性,舊的屬性會(huì)被重命名和刪除。每次更新這種 toString 都會(huì)非常痛苦。我們可以嘗試遍歷其中的屬性,但是如果對(duì)象很復(fù)雜,并且在屬性中嵌套了對(duì)象呢?我們也需要對(duì)它們進(jìn)行轉(zhuǎn)換。

幸運(yùn)的是,不需要編寫代碼來處理所有這些問題。這項(xiàng)任務(wù)已經(jīng)解決了。

JSON.stringify

JSON(JavaScript Object Notation)是表示值和對(duì)象的通用格式。在 RFC 4627 標(biāo)準(zhǔn)中有對(duì)其的描述。最初它是為 JavaScript 而創(chuàng)建的,但許多其他編程語言也有用于處理它的庫。因此,當(dāng)客戶端使用 JavaScript 而服務(wù)器端是使用 Ruby/PHP/Java 等語言編寫的時(shí),使用 JSON 可以很容易地進(jìn)行數(shù)據(jù)交換。

JavaScript 提供了如下方法:

  • ?JSON.stringify? 將對(duì)象轉(zhuǎn)換為 JSON。
  • ?JSON.parse? 將 JSON 轉(zhuǎn)換回對(duì)象。

例如,在這里我們 ?JSON.stringify? 一個(gè) ?student? 對(duì)象:

let student = {
  name: 'John',
  age: 30,
  isAdmin: false,
  courses: ['html', 'css', 'js'],
  spouse: null
};

let json = JSON.stringify(student);

alert(typeof json); // we've got a string!

alert(json);
/* JSON 編碼的對(duì)象:
{
  "name": "John",
  "age": 30,
  "isAdmin": false,
  "courses": ["html", "css", "js"],
  "spouse": null
}
*/

方法 JSON.stringify(student) 接收對(duì)象并將其轉(zhuǎn)換為字符串。

得到的 json 字符串是一個(gè)被稱為 JSON 編碼(JSON-encoded) 或 序列化(serialized) 或 字符串化(stringified) 或 編組化(marshalled) 的對(duì)象。我們現(xiàn)在已經(jīng)準(zhǔn)備好通過有線發(fā)送它或?qū)⑵浞湃肫胀〝?shù)據(jù)存儲(chǔ)。

請(qǐng)注意,JSON 編碼的對(duì)象與對(duì)象字面量有幾個(gè)重要的區(qū)別:

  • 字符串使用雙引號(hào)。JSON 中沒有單引號(hào)或反引號(hào)。所以 ?'John'? 被轉(zhuǎn)換為 ?"John"?。
  • 對(duì)象屬性名稱也是雙引號(hào)的。這是強(qiáng)制性的。所以 ?age:30? 被轉(zhuǎn)換成 ?"age":30?。

JSON.stringify 也可以應(yīng)用于原始(primitive)數(shù)據(jù)類型。

JSON 支持以下數(shù)據(jù)類型:

  • Objects ?{ ... }?
  • Arrays ?[ ... ]?
  • Primitives:
    • strings,
    • numbers,
    • boolean values ?true/false?,
    • ?null?。

例如:

// 數(shù)字在 JSON 還是數(shù)字
alert( JSON.stringify(1) ) // 1

// 字符串在 JSON 中還是字符串,只是被雙引號(hào)擴(kuò)起來
alert( JSON.stringify('test') ) // "test"

alert( JSON.stringify(true) ); // true

alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]

JSON 是語言無關(guān)的純數(shù)據(jù)規(guī)范,因此一些特定于 JavaScript 的對(duì)象屬性會(huì)被 JSON.stringify 跳過。

即:

  • 函數(shù)屬性(方法)。
  • Symbol 類型的鍵和值。
  • 存儲(chǔ) ?undefined? 的屬性。
let user = {
  sayHi() { // 被忽略
    alert("Hello");
  },
  [Symbol("id")]: 123, // 被忽略
  something: undefined // 被忽略
};

alert( JSON.stringify(user) ); // {}(空對(duì)象)

通常這很好。如果這不是我們想要的方式,那么我們很快就會(huì)看到如何自定義轉(zhuǎn)換方式。

最棒的是支持嵌套對(duì)象轉(zhuǎn)換,并且可以自動(dòng)對(duì)其進(jìn)行轉(zhuǎn)換。

例如:

let meetup = {
  title: "Conference",
  room: {
    number: 23,
    participants: ["john", "ann"]
  }
};

alert( JSON.stringify(meetup) );
/* 整個(gè)結(jié)構(gòu)都被字符串化了
{
  "title":"Conference",
  "room":{"number":23,"participants":["john","ann"]},
}
*/

重要的限制:不得有循環(huán)引用。

例如:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: ["john", "ann"]
};

meetup.place = room;       // meetup 引用了 room
room.occupiedBy = meetup; // room 引用了 meetup

JSON.stringify(meetup); // Error: Converting circular structure to JSON

在這里,轉(zhuǎn)換失敗了,因?yàn)檠h(huán)引用:room.occupiedBy 引用了 meetupmeetup.place 引用了 room


排除和轉(zhuǎn)換:replacer

JSON.stringify 的完整語法是:

let json = JSON.stringify(value[, replacer, space])

value

要編碼的值。

replacer

要編碼的屬性數(shù)組或映射函數(shù) ?function(key, value)?。

space

用于格式化的空格數(shù)量

大部分情況,?JSON.stringify? 僅與第一個(gè)參數(shù)一起使用。但是,如果我們需要微調(diào)替換過程,比如過濾掉循環(huán)引用,我們可以使用 ?JSON.stringify? 的第二個(gè)參數(shù)。

如果我們傳遞一個(gè)屬性數(shù)組給它,那么只有這些屬性會(huì)被編碼。

例如:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup 引用了 room
};

room.occupiedBy = meetup; // room 引用了 meetup

alert( JSON.stringify(meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}

這里我們可能過于嚴(yán)格了。屬性列表應(yīng)用于了整個(gè)對(duì)象結(jié)構(gòu)。所以 participants 是空的,因?yàn)?nbsp;name 不在列表中。

讓我們包含除了會(huì)導(dǎo)致循環(huán)引用的 room.occupiedBy 之外的所有屬性:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup 引用了 room
};

room.occupiedBy = meetup; // room 引用了 meetup

alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
  "title":"Conference",
  "participants":[{"name":"John"},{"name":"Alice"}],
  "place":{"number":23}
}
*/

現(xiàn)在,除 occupiedBy 以外的所有內(nèi)容都被序列化了。但是屬性的列表太長(zhǎng)了。

幸運(yùn)的是,我們可以使用一個(gè)函數(shù)代替數(shù)組作為 replacer。

該函數(shù)會(huì)為每個(gè) (key,value) 對(duì)調(diào)用并返回“已替換”的值,該值將替換原有的值。如果值被跳過了,則為 undefined

在我們的例子中,我們可以為 occupiedBy 以外的所有內(nèi)容按原樣返回 value。為了 occupiedBy,下面的代碼返回 undefined

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup 引用了 room
};

room.occupiedBy = meetup; // room 引用了 meetup

alert( JSON.stringify(meetup, function replacer(key, value) {
  alert(`${key}: ${value}`);
  return (key == 'occupiedBy') ? undefined : value;
}));

/* key:value pairs that come to replacer:
:             [object Object]
title:        Conference
participants: [object Object],[object Object]
0:            [object Object]
name:         John
1:            [object Object]
name:         Alice
place:        [object Object]
number:       23
occupiedBy: [object Object]
*/

請(qǐng)注意 replacer 函數(shù)會(huì)獲取每個(gè)鍵/值對(duì),包括嵌套對(duì)象和數(shù)組項(xiàng)。它被遞歸地應(yīng)用。replacer 中的 this 的值是包含當(dāng)前屬性的對(duì)象。

第一個(gè)調(diào)用很特別。它是使用特殊的“包裝對(duì)象”制作的:{"": meetup}。換句話說,第一個(gè) (key, value) 對(duì)的鍵是空的,并且該值是整個(gè)目標(biāo)對(duì)象。這就是上面的示例中第一行是 ":[object Object]" 的原因。

這個(gè)理念是為了給 replacer 提供盡可能多的功能:如果有必要,它有機(jī)會(huì)分析并替換/跳過整個(gè)對(duì)象。

格式化:space

JSON.stringify(value, replacer, spaces) 的第三個(gè)參數(shù)是用于優(yōu)化格式的空格數(shù)量。

以前,所有字符串化的對(duì)象都沒有縮進(jìn)和額外的空格。如果我們想通過網(wǎng)絡(luò)發(fā)送一個(gè)對(duì)象,那就沒什么問題。space 參數(shù)專門用于調(diào)整出更美觀的輸出。

這里的 space = 2 告訴 JavaScript 在多行中顯示嵌套的對(duì)象,對(duì)象內(nèi)部縮進(jìn) 2 個(gè)空格:

let user = {
  name: "John",
  age: 25,
  roles: {
    isAdmin: false,
    isEditor: true
  }
};

alert(JSON.stringify(user, null, 2));
/* 兩個(gè)空格的縮進(jìn):
{
  "name": "John",
  "age": 25,
  "roles": {
    "isAdmin": false,
    "isEditor": true
  }
}
*/

/* 對(duì)于 JSON.stringify(user, null, 4) 的結(jié)果會(huì)有更多縮進(jìn):
{
    "name": "John",
    "age": 25,
    "roles": {
        "isAdmin": false,
        "isEditor": true
    }
}
*/

第三個(gè)參數(shù)也可以是字符串。在這種情況下,字符串用于縮進(jìn),而不是空格的數(shù)量。

?spaces? 參數(shù)僅用于日志記錄和美化輸出。

自定義 “toJSON”

像 toString 進(jìn)行字符串轉(zhuǎn)換,對(duì)象也可以提供 toJSON 方法來進(jìn)行 JSON 轉(zhuǎn)換。如果可用,JSON.stringify 會(huì)自動(dòng)調(diào)用它。

例如:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  date: new Date(Date.UTC(2017, 0, 1)),
  room
};

alert( JSON.stringify(meetup) );
/*
  {
    "title":"Conference",
    "date":"2017-01-01T00:00:00.000Z",  // (1)
    "room": {"number":23}               // (2)
  }
*/

在這兒我們可以看到 date (1) 變成了一個(gè)字符串。這是因?yàn)樗腥掌诙加幸粋€(gè)內(nèi)建的 toJSON 方法來返回這種類型的字符串。

現(xiàn)在讓我們?yōu)閷?duì)象 room 添加一個(gè)自定義的 toJSON

let room = {
  number: 23,
  toJSON() {
    return this.number;
  }
};

let meetup = {
  title: "Conference",
  room
};

alert( JSON.stringify(room) ); // 23

alert( JSON.stringify(meetup) );
/*
  {
    "title":"Conference",
    "room": 23
  }
*/

正如我們所看到的,toJSON 既可以用于直接調(diào)用 JSON.stringify(room) 也可以用于當(dāng) room 嵌套在另一個(gè)編碼對(duì)象中時(shí)。

JSON.parse

要解碼 JSON 字符串,我們需要另一個(gè)方法 JSON.parse

語法:

let value = JSON.parse(str, [reviver]);

str

要解析的 JSON 字符串。

reviver

可選的函數(shù) function(key,value),該函數(shù)將為每個(gè) ?(key, value)? 對(duì)調(diào)用,并可以對(duì)值進(jìn)行轉(zhuǎn)換。

例如:

// 字符串化數(shù)組
let numbers = "[0, 1, 2, 3]";

numbers = JSON.parse(numbers);

alert( numbers[1] ); // 1

對(duì)于嵌套對(duì)象:

let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';

let user = JSON.parse(userData);

alert( user.friends[1] ); // 1

JSON 可能會(huì)非常復(fù)雜,對(duì)象和數(shù)組可以包含其他對(duì)象和數(shù)組。但是它們必須遵循相同的 JSON 格式。

以下是手寫 JSON 時(shí)的典型錯(cuò)誤(有時(shí)我們必須出于調(diào)試目的編寫它):

let json = `{
  name: "John",                     // 錯(cuò)誤:屬性名沒有雙引號(hào)
  "surname": 'Smith',               // 錯(cuò)誤:值使用的是單引號(hào)(必須使用雙引號(hào))
  'isAdmin': false                  // 錯(cuò)誤:鍵使用的是單引號(hào)(必須使用雙引號(hào))
  "birthday": new Date(2000, 2, 3), // 錯(cuò)誤:不允許使用 "new",只能是裸值
  "friends": [0,1,2,3]              // 這個(gè)沒問題
}`;

此外,JSON 不支持注釋。向 JSON 添加注釋無效。

還有另一種名為 JSON5 的格式,它允許未加引號(hào)的鍵,也允許注釋等。但這是一個(gè)獨(dú)立的庫,不在語言的規(guī)范中。

常規(guī)的 JSON 格式嚴(yán)格,并不是因?yàn)樗拈_發(fā)者很懶,而是為了實(shí)現(xiàn)簡(jiǎn)單,可靠且快速地實(shí)現(xiàn)解析算法。

使用 reviver

想象一下,我們從服務(wù)器上獲得了一個(gè)字符串化的 ?meetup? 對(duì)象。

它看起來像這樣:

// title: (meetup title), date: (meetup date)
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

……現(xiàn)在我們需要對(duì)它進(jìn)行 反序列(deserialize),把它轉(zhuǎn)換回 JavaScript 對(duì)象。

讓我們通過調(diào)用 JSON.parse 來完成:

let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

let meetup = JSON.parse(str);

alert( meetup.date.getDate() ); // Error!

?。?bào)錯(cuò)了!

meetup.date 的值是一個(gè)字符串,而不是 Date 對(duì)象。JSON.parse 怎么知道應(yīng)該將字符串轉(zhuǎn)換為 Date 呢?

讓我們將 reviver 函數(shù)傳遞給 JSON.parse 作為第二個(gè)參數(shù),該函數(shù)按照“原樣”返回所有值,但是 date 會(huì)變成 Date

let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

let meetup = JSON.parse(str, function(key, value) {
  if (key == 'date') return new Date(value);
  return value;
});

alert( meetup.date.getDate() ); // 現(xiàn)在正常運(yùn)行了!

順便說一下,這也適用于嵌套對(duì)象:

let schedule = `{
  "meetups": [
    {"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
    {"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
  ]
}`;

schedule = JSON.parse(schedule, function(key, value) {
  if (key == 'date') return new Date(value);
  return value;
});

alert( schedule.meetups[1].date.getDate() ); // 正常運(yùn)行了!

總結(jié)

  • JSON 是一種數(shù)據(jù)格式,具有自己的獨(dú)立標(biāo)準(zhǔn)和大多數(shù)編程語言的庫。
  • JSON 支持 object,array,string,number,boolean 和 ?null?。
  • JavaScript 提供序列化(serialize)成 JSON 的方法 JSON.stringify 和解析 JSON 的方法 JSON.parse。
  • 這兩種方法都支持用于智能讀/寫的轉(zhuǎn)換函數(shù)。
  • 如果一個(gè)對(duì)象具有 ?toJSON?,那么它會(huì)被 ?JSON.stringify? 調(diào)用。

任務(wù)


將對(duì)象轉(zhuǎn)換為 JSON,然后再轉(zhuǎn)換回來

重要程度: 5

將 ?user? 轉(zhuǎn)換為 JSON,然后將其轉(zhuǎn)換回到另一個(gè)變量。

let user = {
  name: "John Smith",
  age: 35
};

解決方案

let user = {
  name: "John Smith",
  age: 35
};

let user2 = JSON.parse(JSON.stringify(user));

排除反向引用

重要程度: 5

在簡(jiǎn)單循環(huán)引用的情況下,我們可以通過名稱排除序列化中違規(guī)的屬性。

但是,有時(shí)我們不能只使用名稱,因?yàn)樗瓤赡茉谘h(huán)引用中也可能在常規(guī)屬性中使用。因此,我們可以通過屬性值來檢查屬性。

編寫 replacer 函數(shù),移除引用 meetup 的屬性,并將其他所有屬性序列化:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  occupiedBy: [{name: "John"}, {name: "Alice"}],
  place: room
};

// 循環(huán)引用
room.occupiedBy = meetup;
meetup.self = meetup;

alert( JSON.stringify(meetup, function replacer(key, value) {
  /* your code */
}));

/* 結(jié)果應(yīng)該是:
{
  "title":"Conference",
  "occupiedBy":[{"name":"John"},{"name":"Alice"}],
  "place":{"number":23}
}
*/

解決方案

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  occupiedBy: [{name: "John"}, {name: "Alice"}],
  place: room
};

room.occupiedBy = meetup;
meetup.self = meetup;

alert( JSON.stringify(meetup, function replacer(key, value) {
  return (key != "" && value == meetup) ? undefined : value;
}));

/*
{
  "title":"Conference",
  "occupiedBy":[{"name":"John"},{"name":"Alice"}],
  "place":{"number":23}
}
*/

這里我們還需要判斷 key=="" 以排除第一個(gè)調(diào)用時(shí) value 是 meetup 的情況。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)