假設我們有一個複雜的物件,我們想要將它轉換成字串,以便透過網路傳送,或僅是為了記錄目的而輸出它。
當然,這樣的字串應該包含所有重要的屬性。
我們可以像這樣實作轉換
let user = {
name: "John",
age: 30,
toString() {
return `{name: "${this.name}", age: ${this.age}}`;
}
};
alert(user); // {name: "John", age: 30}
…但在開發過程中,會新增新的屬性,舊的屬性會重新命名並移除。每次更新這樣的 toString
都會變成一種痛苦。我們可以嘗試在其中迴圈屬性,但如果物件很複雜,而且屬性中有巢狀物件怎麼辦?我們也需要實作它們的轉換。
幸運的是,無需撰寫程式碼來處理所有這些。此任務已解決。
JSON.stringify
JSON(JavaScript 物件表示法)是一種通用的格式,用於表示值和物件。它在 RFC 4627 標準中描述。最初它是為 JavaScript 製作的,但許多其他語言也有函式庫來處理它。因此,當客戶端使用 JavaScript 而伺服器以 Ruby/PHP/Java/Whatever 編寫時,很容易使用 JSON 進行資料交換。
JavaScript 提供方法
JSON.stringify
將物件轉換為 JSON。JSON.parse
將 JSON 轉換回物件。
例如,這裡我們 JSON.stringify
一個學生
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-encoded object:
{
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"spouse": null
}
*/
方法 JSON.stringify(student)
取得物件並將其轉換為字串。
產生的 json
字串稱為JSON 編碼或序列化或字串化或封送的物件。我們準備好透過網路傳送它或將其放入純粹資料儲存中。
請注意,JSON 編碼物件與物件文字有幾個重要的差異
- 字串使用雙引號。JSON 中沒有單引號或反引號。因此
'John'
變成"John"
。 - 物件屬性名稱也使用雙引號。這是強制性的。因此
age:30
變成"age":30
。
JSON.stringify
也可套用於基本型別。
JSON 支援下列資料類型
- 物件
{ ... }
- 陣列
[ ... ]
- 基本型別
- 字串,
- 數字,
- 布林值
true/false
, null
.
例如
// a number in JSON is just a number
alert( JSON.stringify(1) ) // 1
// a string in JSON is still a string, but double-quoted
alert( JSON.stringify('test') ) // "test"
alert( JSON.stringify(true) ); // true
alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]
JSON 是僅限資料的語言非依賴性規格,因此 JSON.stringify
會略過一些特定於 JavaScript 的物件屬性。
即
- 函式屬性(方法)。
- 符號鍵和值。
- 儲存
undefined
的屬性。
let user = {
sayHi() { // ignored
alert("Hello");
},
[Symbol("id")]: 123, // ignored
something: undefined // ignored
};
alert( JSON.stringify(user) ); // {} (empty object)
通常這樣就可以了。如果這不是我們想要的,那麼我們很快就會看到如何自訂這個程序。
很棒的是,它支援並自動轉換巢狀物件。
例如
let meetup = {
title: "Conference",
room: {
number: 23,
participants: ["john", "ann"]
}
};
alert( JSON.stringify(meetup) );
/* The whole structure is stringified:
{
"title":"Conference",
"room":{"number":23,"participants":["john","ann"]},
}
*/
重要的限制:不能有循環參考。
例如
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: ["john", "ann"]
};
meetup.place = room; // meetup references room
room.occupiedBy = meetup; // room references meetup
JSON.stringify(meetup); // Error: Converting circular structure to JSON
在此,轉換失敗,因為循環參考:room.occupiedBy
參考 meetup
,而 meetup.place
參考 room
排除與轉換:替換器
JSON.stringify
的完整語法為
let json = JSON.stringify(value[, replacer, space])
- 值
- 要編碼的值。
- 替換器
- 要編碼的屬性陣列或對應函式
function(key, value)
。 - 空格
- 用於格式化的空格數量
大多數時候,JSON.stringify
只會使用第一個參數。但如果我們需要微調替換程序,例如過濾循環參照,我們可以使用 JSON.stringify
的第二個參數。
如果我們傳遞一個屬性陣列給它,則只會編碼這些屬性。
例如
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
room.occupiedBy = meetup; // room references meetup
alert( JSON.stringify(meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}
這裡我們可能太嚴格了。屬性清單套用於整個物件結構。因此 participants
中的物件是空的,因為清單中沒有 name
。
讓我們在清單中包含每個屬性,除了會導致循環參照的 room.occupiedBy
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
room.occupiedBy = meetup; // room references meetup
alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
}
*/
現在除了 occupiedBy
之外,一切都已序列化。但屬性清單很長。
幸運的是,我們可以使用函式而不是陣列作為 replacer
。
函式將針對每個 (key, value)
對呼叫,並應傳回「替換」值,該值將用於取代原始值。或者如果要略過該值,則傳回 undefined
。
在我們的案例中,我們可以對除了 occupiedBy
之外的所有內容傳回「原樣」的 value
。若要忽略 occupiedBy
,以下程式碼會傳回 undefined
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
room.occupiedBy = meetup; // room references 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]
*/
請注意,replacer
函式會取得每個鍵/值對,包括巢狀物件和陣列項目。它會遞迴套用。replacer
內部的 this
值是包含目前屬性的物件。
第一次呼叫很特別。它是使用特殊「包裝物件」進行的:{"": meetup}
。換句話說,第一個 (key, value)
對有一個空鍵,而值是整個目標物件。這就是為什麼在上面的範例中,第一行是 ":[object Object]"
。
這個想法是盡可能為 replacer
提供更多功能:它有機會分析和替換/略過整個物件,如果需要的話。
格式化:空格
JSON.stringify(value, replacer, space)
的第三個參數是要用於美化格式化的空格數量。
先前,所有字串化的物件都沒有縮排和額外的空格。如果我們要透過網路傳送物件,這樣很好。space
參數專門用於美觀的輸出。
這裡 space = 2
告訴 JavaScript 在多行上顯示巢狀物件,物件內部縮排 2 個空格
let user = {
name: "John",
age: 25,
roles: {
isAdmin: false,
isEditor: true
}
};
alert(JSON.stringify(user, null, 2));
/* two-space indents:
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
/* for JSON.stringify(user, null, 4) the result would be more indented:
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
第三個參數也可以是字串。在這種情況下,字串會用於縮排,而不是空格數。
space
參數僅用於記錄和美觀輸出的目的。
自訂「toJSON」
就像用於字串轉換的 toString
,物件可以提供 toJSON
方法進行轉換為 JSON。如果可用,JSON.stringify
會自動呼叫它。
例如
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)
變成字串了。這是因為所有日期都有內建的 toJSON
方法,會傳回此類型的字串。
現在,讓我們為我們的物件 room
(2)
新增自訂的 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
用於直接呼叫 JSON.stringify(room)
,以及當 room
巢狀在另一個編碼物件中時。
JSON.parse
要解碼 JSON 字串,我們需要另一個名為 JSON.parse 的方法。
語法
let value = JSON.parse(str[, reviver]);
- str
- 要解析的 JSON 字串。
- reviver
- 可選的函式 (key,value),將會對每個
(key, value)
配對呼叫,並可以轉換值。
例如
// stringified array
let numbers = "[0, 1, 2, 3]";
numbers = JSON.parse(numbers);
alert( numbers[1] ); // 1
或用於巢狀物件
let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';
let user = JSON.parse(userData);
alert( user.friends[1] ); // 1
JSON 可能會複雜到必要程度,物件和陣列可以包含其他物件和陣列。但它們必須遵守相同的 JSON 格式。
以下是手寫 JSON 中常見的錯誤(有時我們必須為了除錯目的而寫它)
let json = `{
name: "John", // mistake: property name without quotes
"surname": 'Smith', // mistake: single quotes in value (must be double)
'isAdmin': false // mistake: single quotes in key (must be double)
"birthday": new Date(2000, 2, 3), // mistake: no "new" is allowed, only bare values
"friends": [0,1,2,3] // here all fine
}`;
此外,JSON 不支援註解。在 JSON 中新增註解會使它無效。
還有一個名為 JSON5 的格式,允許未加引號的鍵、註解等。但這是一個獨立的函式庫,不在語言規格中。
常規 JSON 如此嚴格,並不是因為它的開發人員很懶,而是為了允許解析演算法容易、可靠且非常快速地實作。
使用 reviver
想像一下,我們從伺服器取得一個字串化的 meetup
物件。
它看起來像這樣
// title: (meetup title), date: (meetup date)
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
…現在我們需要將它「反序列化」,轉回 JavaScript 物件。
讓我們呼叫 JSON.parse
來執行此動作
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
let meetup = JSON.parse(str);
alert( meetup.date.getDate() ); // Error!
糟糕!發生錯誤了!
meetup.date
的值是字串,而不是 Date
物件。JSON.parse
怎麼知道它應該將該字串轉換為 Date
?
讓我們將復活函式作為第二個引數傳遞給 JSON.parse
,它會將所有值「原樣」傳回,但 date
會變成 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() ); // now works!
順帶一提,這也適用於巢狀物件
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() ); // works!
摘要
- JSON 是一種資料格式,它有自己的獨立標準,且支援大部分程式語言的函式庫。
- JSON 支援一般物件、陣列、字串、數字、布林值和
null
。 - JavaScript 提供方法 JSON.stringify 將資料序列化成 JSON,以及 JSON.parse 從 JSON 讀取資料。
- 這兩個方法都支援轉換器函式,以進行智慧化讀取/寫入。
- 如果物件有
toJSON
,則JSON.stringify
會呼叫它。
留言
<code>
標籤,若要插入多行,請將它們包在<pre>
標籤中,若要插入 10 行以上的程式碼,請使用沙盒(plnkr、jsbin、codepen…)