W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
假設(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(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ū)別:
'John'
? 被轉(zhuǎn)換為 ?"John"
?。age:30
? 被轉(zhuǎn)換成 ?"age":30
?。JSON.stringify
也可以應(yīng)用于原始(primitive)數(shù)據(jù)類型。
JSON 支持以下數(shù)據(jù)類型:
{ ... }
?[ ... ]
?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
跳過。
即:
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
引用了 meetup
,meetup.place
引用了 room
:
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ì)象。
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ù)僅用于日志記錄和美化輸出。
像 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 字符串,我們需要另一個(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)解析算法。
想象一下,我們從服務(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)行了!
null
?。toJSON
?,那么它會(huì)被 ?JSON.stringify
? 調(diào)用。將 ?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));
在簡(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
的情況。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: