js與json互動理論和樣本
JSON 的文法可以表示以下三種類型的值。
? 簡單值:使用與 JavaScript 相同的文法,可以在 JSON 中表示字串、數值、布爾值和 null 。
但 JSON 不支援 JavaScript 中的特殊值 undefined 。
? 對象:對象作為一種複雜資料類型,表示的是一組無序的索引值對兒。而每個索引值對兒中的值可
以是簡單值,也可以是複雜資料類型的值。
? 數組:數組也是一種複雜資料類型,表示一組有序的值的列表,可以通過數值索引來訪問其中
的值。數組的值也可以是任意類型——簡單值、對象或數組。
JSON 不支援變數、函數或對象執行個體,它就是一種表示結構化資料的格式,雖然與 JavaScript 中表示
資料的某些文法相同,但它並不局限於 JavaScript 的範疇。
20.1.1 簡單值
最簡單的 JSON 資料形式就是簡單值。例如,下面這個值是有效 JSON 資料:
5
這是 JSON 表示數值 5 的方式。類似地,下面是 JSON 表示字串的方式:
Hello world!
JavaScript 字串與 JSON 字串的最大區別在於,JSON 字串必須使用雙引號(單引號會導致語
法錯誤) 。
布爾值和 null 也是有效 JSON 形式。但是,在實際應用中,JSON 更多地用來表示更複雜的資料
結構,而簡單值只是整個資料結構中的一部分。
20.1.2 對象
JSON 中的對象與 JavaScript 字面量稍微有一些不同。下面是一個 JavaScript 中的對象字面量:
var person = {
name: Nicholas,
age: 29
};
這雖然是開發人員在 JavaScript 中建立對象字面量的標準方式,但 JSON 中的對象要求給屬性加引
號。實際上,在 JavaScript 中,前面的對象字面量完全可以寫成下面這樣:
var object = {
name: Nicholas,
age: 29
};
JSON 表示上述對象的方式如下:
{
name: Nicholas,
age: 29
}
與 JavaScript 的對象字面量相比,JSON 對象有兩個地方不一樣。
首先,沒有聲明變數(JSON 中沒有變數的概念) 。
其次,沒有末尾的分號(因為這不是 JavaScript 語句,所以不需要分號) 。
再說一遍,
對象的屬性必須加雙引號,這在 JSON 中是必需的。屬性的值可以是簡單值,也可以是複雜類型值,因
此可以像下面這樣在對象中內嵌物件:
{
name: Nicholas,
age: 29,
school: {
name: Merrimack College,
location: North Andover, MA
}
}
這個例子在頂級對象中嵌入了學校( school )資訊。雖然有兩個 name 屬性,但由於它們分別
屬於不同的對象,因此這樣完全沒有問題。不過,同一個對象中絕對不應該出現兩個同名屬性。
與 JavaScript 不同,JSON 中對象的屬性名稱任何時候都必須加雙引號。手工編寫 JSON 時,忘了給對
象屬性名稱加雙引號或者把雙引號寫成單引號都是常見的錯誤。
20.1.3 數組
JSON 中的第二種複雜資料類型是數組。JSON 數組採用的就是 JavaScript 中的數組字面量形式。例
如,下面是 JavaScript 中的數組字面量:
var values = [25, hi, true];
在 JSON 中,可以採用同樣的文法表示同一個數組:
[25, hi, true]
同樣要注意,JSON 數組也沒有變數和分號。把數組和對象結合起來,可以構成更複雜的資料集合,
例如:
[
{
title: Professional JavaScript,
authors: [
Nicholas C. Zakas
],
edition: 3,
year: 2011
},
{
title: Professional JavaScript,
authors: [
Nicholas C. Zakas
],
edition: 2,
year: 2009
},
{
title: Professional Ajax,
authors: [
Nicholas C. Zakas,
Jeremy McPeak,
Joe Fawcett
],
edition: 2,
year: 2008
},
{
title: Professional Ajax,
authors: [
Nicholas C. Zakas,
Jeremy McPeak,
Joe Fawcett
],
edition: 1,
year: 2007
},
{
title: Professional JavaScript,
authors: [
Nicholas C. Zakas
],
edition: 1,
year: 2006
}
]
這個數組中包含一些表示圖書的對象。每個對象都有幾個屬性,其中一個屬性是 authors ,這個
屬性的值又是一個數組。對象和數組通常是 JSON 資料結構的最外層形式(當然,這不是強制規定的) ,
利用它們能夠創造出各種各樣的資料結構。
20.2 解析與序列化
JSON 之所以流行,擁有與 JavaScript 類似的文法並不是全部原因。更重要的一個原因是,可以把
JSON 資料結構解析為有用的 JavaScript 對象。與 XML 資料結構要解析成 DOM 文檔而且從中提取資料
極為麻煩相比,JSON 可以解析為 JavaScript 對象的優勢極其明顯。就以上一節中包含一組圖書的 JSON
資料結構為例,在解析為 JavaScript 對象後,只需要下面一行簡單的代碼就可以取得第三本書的書名:
books[2].title
當然, 這裡是假設把解析 JSON 資料結構後得到的對象儲存到了變數 books 中。 再看看下面在 DOM
結構中尋找資料的代碼:
doc.getElementsByTagName(book)[2].getAttribute(title)
看看這些多餘的方法調用,就不難理解為什麼 JSON 能得到 JavaScript 開發人員的熱烈歡迎了。從
此以後,JSON 就成了 Web 服務開發中交換資料的事實標準。
20.2.1 JSON對象
早期的 JSON 解析器基本上就是使用 JavaScript 的 eval() 函數。由於 JSON 是 JavaScript 文法的子
集,因此 eval() 函數可以解析、解釋並返回 JavaScript 對象和數組。ECMAScript 5 對解析 JSON 的行
為進行規範,定義了全域對象 JSON 。支援這個對象的瀏覽器有 IE 8+、Firefox 3.5+、Safari 4+、Chrome
和 Opera 10.5+。 對於較早版本的瀏覽器, 可以使用一個 shim: https://github.com/douglascrockford/JSON-js。
在舊版本的瀏覽器中,使用 eval() 對 JSON 資料結構求值存在風險,因為可能會執行一些惡意代碼。
對於不能原生支援 JSON 解析的瀏覽器,使用這個 shim 是最佳選擇。
JSON 對象有兩個方法: stringify() 和 parse() 。在最簡單的情況下,這兩個方法分別用於把
JavaScript 對象序列化為 JSON 字串和把 JSON 字串解析為原生 JavaScript 值。例如:
var book = {
title: Professional JavaScript,
authors: [
Nicholas C. Zakas
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book);
這個例子使用 JSON.stringify() 把一個 JavaScript 對象序列化為一個 JSON 字串,然後將它保
存在變數 jsonText 中。預設情況下, JSON.stringify() 輸出的 JSON 字串不包含任何空白字元或
縮排,因此儲存在 jsonText 中的字串如下所示:
{title:Professional JavaScript,authors:[Nicholas C. Zakas],edition:3,year:2011}
在序列化 JavaScript 對象時,所有函數及原型成員都會被有意忽略,不體現在結果中。此外,值為
undefined 的任何屬性也都會被跳過。結果中最終都是值為有效 JSON 資料類型的執行個體屬性。
將 JSON 字串直接傳遞給 JSON.parse() 就可以得到相應的 JavaScript 值。例如,使用下列代碼
就可以建立與 book 類似的對象:
var bookCopy = JSON.parse(jsonText);
注意,雖然 book 與 bookCopy 具有相同的屬性,但它們是兩個獨立的、沒有任何關係的對象。
如果傳給 JSON.parse() 的字串不是有效 JSON,該方法會拋出錯誤。
20.2.2 序列化選項
實際上, JSON.stringify() 除了要序列化的 JavaScript 對象外,還可以接收另外兩個參數,這兩
個參數用於指定以不同的方式序列化 JavaScript 對象。第一個參數是個過濾器,可以是一個數組,也可
以是一個函數;第二個參數是一個選項,表示是否在 JSON 字串中保留縮排。單獨或組合使用這兩個
參數,可以更全面深入地控制 JSON 的序列化。
1. 過濾結果
如果過濾器參數是數組,那麼 JSON.stringify() 的結果中將只包含數組中列出的屬性。來看下
面的例子。
var book = {
title: Professional JavaScript,
authors: [
Nicholas C. Zakas
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book, [title, edition]);
JSONStringifyExample01.htm
JSON.stringify() 的第二個參數是一個數組,其中包含兩個字串: title 和 edition 。這
兩個屬性與將要序列化的對象中的屬性是對應的, 因此在返回的結果字串中, 就只會包含這兩個屬性:
{title:Professional JavaScript,edition:3}
如果第二個參數是函數,行為會稍有不同。傳入的函數接收兩個參數,屬性(鍵)名和屬性值。根
據屬性(鍵)名可以知道應該如何處理要序列化的對象中的屬性。屬性名稱只能是字串,而在值並非索引值對兒結構的值時,鍵名可以是Null 字元串。
為了改變序列化對象的結果,函數返回的值就是相應鍵的值。不過要注意,如果函數返回了
undefined ,那麼相應的屬性會被忽略。還是看一個例子吧。
var book = {
title: Professional JavaScript,
authors: [Nicholas C. Zakas,xxxx.xxx],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book,
function(key, value) {
switch (key) {
case authors:
return value.join(,)
case year:
return 5000;
case edition:
return undefined;
default:
return value;
}
});
console.log(jsonText);//{title:Professional JavaScript,authors:Nicholas C. Zakas,xxxx.xxx,year:5000}
這裡,函數過濾器根據傳入的鍵來決定結果。如果鍵為 authors ,就將數組串連為一個字串;
如果鍵為 year ,則將其值設定為 5000 ;如果鍵為 edition ,通過返回 undefined 刪除該屬性。
最後,一定要提供 default 項,此時返回傳入的值,以便其他值都能正常出現在結果中。實際上,第
一次調用這個函數過濾器,傳入的鍵是一個Null 字元串,而值就是 book 對象。序列化後的 JSON 字串
如下所示:
{title:Professional JavaScript,authors:Nicholas C. Zakas,year:5000}
要序列化的對象中的每一個對象都要經過過濾器, 因此數組中的每個帶有這些屬性的對象經過過濾
之後,每個對象都只會包含 title 、 authors 和 year 屬性。
Firefox 3.5 和 3.6 對 JSON.stringify() 的實現有一個 bug,在將函數作為該方法的第二個參數時
這個 bug 就會出現,即這個函數只能作為過濾器:返回 undefined 意味著要跳過某個屬性,而返回其
他任何值都會在結果中包含相應的屬性。Firefox 4 修複了這個 bug。
2. 字串縮排
JSON.stringify() 方法的第三個參數用於控制結果中的縮排和空白符。如果這個參數是一個數
值,那它表示的是每個層級縮排的空格數。例如,要在每個層級縮排 4 個空格,可以這樣寫代碼:
var book = {
title: Professional JavaScript,
authors: [
Nicholas C. Zakas
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book, null, 4);
JSONStringifyExample03.htm
儲存在 jsonText 中的字串如下所示:
{
title: Professional JavaScript,
authors: [
Nicholas C. Zakas
],
edition: 3,
year: 2011
}
不知道讀者注意到沒有, JSON.stringify() 也在結果字串中插入了分行符號以提高可讀性。只要
傳入有效控制縮排的參數值,結果字串就會包含分行符號。 (只縮排而不換行意義不大。 )最大縮排空
格數為 10,所有大於 10 的值都會自動轉換為 10。
如果縮排參數是一個字串而非數值,則這個字串將在 JSON 字串中被用作縮排字元(不再使
用空格) 。在使用字串的情況下,可以將縮排字元設定為定位字元,或者兩個短劃線之類的任一字元。
var jsonText = JSON.stringify(book, null, - -);
這樣, jsonText 中的字串將變成如下所示:
{
--title: Professional JavaScript,
--authors: [
----Nicholas C. Zakas
--],
--edition: 3,
--year: 2011
}
縮排字串最長不能超過 10 個字元長。如果字串長度超過了 10 個,結果中將只出現前 10 個字
符。
3. toJSON() 方法
有時候, JSON.stringify() 還是不能滿足對某些對象進行自訂序列化的需求。在這些情況下,
可以給對象定義 toJSON() 方法, 返回其自身的 JSON 資料格式。 原生 Date 對象有一個 toJSON() 方法,
能夠將JavaScript的 Date 對象自動轉換成ISO 8601日期文字 (與在 Date 對象上調用 toISOString()
的結果完全一樣) 。可以為任何對象添加 toJSON() 方法,比如:
var book = {
title: Professional JavaScript,
authors: [Nicholas C. Zakas],
edition: 3,
year: 2011,
toJSON: function() {
return this.title;
}
};
var jsonText = JSON.stringify(book);
console.log(jsonText);// Professional JavaScript
以上代碼在 book 對象上定義了一個 toJSON() 方法,該方法返回圖書的書名。與 Date 對象類似,
這個對象也將被序列化為一個簡單的字串而非對象。可以讓 toJSON() 方法返回任何值,它都能正常
工作。比如,可以讓這個方法返回 undefined ,此時如果包含它的對象嵌入在另一個對象中,會導致
它的值變成 null ,而如果它是頂級對象,結果就是 undefined 。
toJSON() 可以作為函數過濾器的補充,因此理解序列化的內部順序十分重要。假設把一個對象傳
入 JSON.stringify() ,序列化該對象的順序如下。
(1) 如果存在 toJSON() 方法而且能通過它取得有效值,則調用該方法。否則,返回對象本身。
(2) 如果提供了第二個參數,應用這個函數過濾器。傳入函數過濾器的值是第(1)步返回的值。
(3) 對第(2)步返回的每個值進行相應的序列化。
(4) 如果提供了第三個參數,執行相應的格式化。
無論是考慮定義 toJSON() 方法,還是考慮使用函數過濾器,亦或需要同時使用兩者,理解這個順
序都是至關重要的。
20.2.3 解析選項
JSON.parse() 方法也可以接收另一個參數,該參數是一個函數,將在每個索引值對兒上調用。為了
區別 JSON.stringify() 接收的替換(過濾)函數(replacer) ,這個函數被稱為還原函數(reviver) ,
但實際上這兩個函數的簽名是相同的——它們都接收兩個參數,一個鍵和一個值,而且都需要返回一
個值。
如果還原函數返回 undefined ,則表示要從結果中刪除相應的鍵;如果返回其他值,則將該值插
入到結果中。在將日期文字轉換為 Date 對象時,經常要用到還原函數。例如:
var book = {
title: Professional JavaScript,
authors: [Nicholas C. Zakas],
edition: 3,
year: 2011,
releaseDate: new Date(2011, 11, 1) //新增
};
var jsonText = JSON.stringify(book);
var bookCopy = JSON.parse(jsonText,
function(key, value) {
if (key == releaseDate) {
return new Date(value);
} else {
return value;
}
});
console.log(bookCopy.releaseDate.getFullYear()); //2011
以上代碼先是為 book 對象新增了一個 releaseDate 屬性,該屬性儲存著一個 Date 對象。這個
對象在經過序列化之後變成了有效 JSON 字串,然後經過解析又在 bookCopy 中還原為一個 Date
對象。還原函數在遇到 releaseDate 鍵時,會基於相應的值建立一個新的 Date 對象。結果就是
bookCopy.releaseDate 屬性中會儲存一個 Date 對象。正因為如此,才能基於這個對象調用
getFullYear() 方法。