??曾經(jīng)有一段時(shí)間,XML 是互聯(lián)網(wǎng)上傳輸數(shù)據(jù)化結(jié)構(gòu)的事實(shí)標(biāo)準(zhǔn)耍鬓。Web 服務(wù)的第一次浪潮很大程度上都是建立在 XML 之上的阔籽,突出的特點(diǎn)是服務(wù)器與服務(wù)器間通信。
??然而牲蜀,業(yè)界一直不乏質(zhì)疑 XML 的聲音笆制。不少人認(rèn)為 XML 過于繁瑣、冗長涣达。為解決這個(gè)問題项贺,也涌現(xiàn)了一些方案。不過峭判,Web 的發(fā)展方向已經(jīng)改變了开缎。
??2006年,Douglas Crockford 把 JSON(JavaScript Object Notation林螃,JavaScript 對象表示法)作為 IETF RFC 4627 提交給 IETF奕删,而 JSON 的應(yīng)用早在 2001 年就已經(jīng)開始了。
??JSON 是 JavaScript 是一個(gè)嚴(yán)格的子集疗认,利用了 JavaScript 中的一些模式來表示結(jié)構(gòu)化數(shù)據(jù)完残。
??Crockford 認(rèn)為與 XML 相比,JSON 是在 JavaScript 中讀寫結(jié)構(gòu)化數(shù)據(jù)的更好的方式横漏。因?yàn)榭梢园?JSON 直接傳給 eval()谨设,而且不必創(chuàng)建 DOM 對象。
??關(guān)于JSON缎浇,最重要的是要理解它是一種數(shù)據(jù)格式扎拣,不是一種編程語言。雖然具有相同的語法形式,但 JSON 并不從屬于 JavaScript二蓝。
??而且誉券,并不是只有 JavaScript 才使用 JSON,畢竟 JSON 只是一種數(shù)據(jù)格式刊愚。很多編程語言都有針對 JSON 的解析器和序列化器踊跟。
1、語法
??JSON 的語法可以表示三種類型的值鸥诽。
- 簡單值商玫;使用與 JavaScript 相同的語法,可以在 JSON 中表示字符串牡借、數(shù)值拳昌、布爾值和 null。但 JSON 不支持 JavaScript 中的特殊值 undefined蓖捶、
- 對象:對象作為一種復(fù)雜數(shù)據(jù)類型地回,表示的是一組無序的鍵值對兒。而每個(gè)簡直對兒中的值可以是簡單值俊鱼,也可以是復(fù)雜數(shù)據(jù)類型的值刻像。
- 數(shù)組:數(shù)組也是一種復(fù)雜數(shù)據(jù)類型,表示一組有序的值的列表并闲,可以通過數(shù)值索引來訪問其中的值细睡。數(shù)組的值也可以是任意類型——簡單值、對象或數(shù)組帝火。
??JSON 不支持變量溜徙、函數(shù)或?qū)ο髮?shí)例,它就是一種表示結(jié)構(gòu)化數(shù)據(jù)的格式犀填,雖然與 JavaScript 中表示數(shù)據(jù)的某些語法相同蠢壹,但它并不局限于 JavaScript 的范疇。
1.1九巡、簡單值
??最簡單的 JSON 數(shù)據(jù)形式就是簡單值图贸。例如,下面這個(gè)值是有效的 JSON 數(shù)據(jù):
5
??這是 JSON 表示數(shù)值 5 的方式冕广。類似地疏日,下面是 JSON 表示字符串的方式:
"Hello world!"
??JavaScript 字符串與 JSON 字符串的最大區(qū)別在于,JSON 字符串必須使用雙引號(hào)(單引號(hào)會(huì)導(dǎo)致語法錯(cuò)誤)撒汉。
??布爾值和 null 也是有效的 JSON 形式沟优。但是,在實(shí)際應(yīng)用中睬辐,JSON 更多地用來表示更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)挠阁,而簡單值只是整個(gè)數(shù)據(jù)結(jié)構(gòu)中的一部分宾肺。
1.2、對象
??JSON 中的對象與 JavaScript 字面量稍微有一些不同鹃唯。下面是一個(gè) JavaScript 中的對象字面量:
var person = {
name: "Nicholas",
age: 29
};
??這雖然是開發(fā)人員在 JavaScript 中創(chuàng)建對象字面量的標(biāo)準(zhǔn)方式爱榕,但 JSON 中的對象要求給屬性加引號(hào)瓣喊。實(shí)際上坡慌,在 JavaScript 中,前面的對象字面量完全可以寫成下面這樣:
var person = {
"name": "Nicholas",
"age": 29
};
??JSON 表示上述對象的方式如下:
{
"name": "Nicholas",
"age": 29
}
??與 JavaScript 的對象字面量相比藻三,JSON 對象有兩個(gè)地方不一樣洪橘。首先,沒有聲明變量(JSON 中沒有變量的概念)棵帽。其次熄求,沒有末尾的分號(hào)(因?yàn)檫@不是 JavaScript 語句,所以不需要分號(hào))逗概。
??再說一遍弟晚,對象的屬性必須加雙引號(hào),這在 JSON 中是必需的逾苫。屬性的值可以是簡單值卿城,也可以是復(fù)雜類型值,因此可以像下面這樣在對象中嵌入對象:
[
{
"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
}
]
??這個(gè)數(shù)組中包含一些表示圖書的對象铅搓。每個(gè)對象都有幾個(gè)屬性瑟押,其中一個(gè)屬性是"authors",這個(gè)屬性的值又是一個(gè)數(shù)組星掰。對象和數(shù)組通常是 JSON 數(shù)據(jù)結(jié)構(gòu)的最外層形式(當(dāng)然多望,這不是強(qiáng)制規(guī)定的),利用它們能夠創(chuàng)造出各種各樣的數(shù)據(jù)結(jié)構(gòu)氢烘。
2怀偷、解析與序列化
??JSON 之所以流行,擁有與 JavaScript 類似的語法并不是全部原因播玖。更重要的一個(gè)原因是椎工,可以把 JSON 數(shù)據(jù)結(jié)構(gòu)解析為有用的 JavaScript 對象。
??與 XML 數(shù)據(jù)結(jié)構(gòu)要解析成 DOM 文檔而且從中提取數(shù)據(jù)極為麻煩相比黎棠,JSON 可以解析為 JavaScript 對象的優(yōu)勢極其明顯晋渺。就以上一節(jié)中包含一組圖書的 JSON 數(shù)據(jù)結(jié)構(gòu)為例,在解析為 JavaScript 對象后脓斩,只需要下面一行簡單的代碼就可以取得第三本書的書名:
books[2].title
??當(dāng)然木西,這里是假設(shè)把解析 JSON 數(shù)據(jù)結(jié)構(gòu)后得到的對象保存到了變量 books 中。
??再看看下面在 DOM 結(jié)構(gòu)中查找數(shù)據(jù)的代碼:
doc.getElementsByTagName("book")[2].getAttribute("title")
??看看這些多余的方法調(diào)用随静,就不難理解為什么 JSON 能得到 JavaScript 開發(fā)人員的熱烈歡迎了八千。從此以后吗讶,JSON 就成了 Web 服務(wù)開發(fā)中交換數(shù)據(jù)的事實(shí)標(biāo)準(zhǔn)。
2.1恋捆、JSON 對象
??早期的 JSON 解析器基本上就是使用 JavaScript 的 eval() 函數(shù)照皆。由于 JSON 是 JavaScript 語法的子集,因此 eval() 函數(shù)可以解析沸停、解釋并返回 JavaScript 對象和數(shù)組膜毁。
??ECMAScript 5 對解析 JSON 的行為進(jìn)行規(guī)范,定義了全局對象 JSON愤钾。支持這個(gè)對象的瀏覽器有 IE 8+瘟滨、Firefox 3.5+、Safari 4+能颁、Chrome 和 Opera 10.5+杂瘸。
??對于較早版本的瀏覽器,可以使用一個(gè) shim:https://github.com/douglascrockford/JSON-js伙菊。
??在舊版本的瀏覽器中败玉,使用 eval() 對 JSON 數(shù)據(jù)結(jié)構(gòu)求值存在風(fēng)險(xiǎn),因?yàn)榭赡軙?huì)執(zhí)行一些惡意代碼镜硕。
??對于不能原生支持 JSON 解析的瀏覽器运翼,使用這個(gè) shim 是最佳選擇。
??JSON 對象有兩個(gè)方法:stringify() 和 parse()谦疾。在最簡單的情況下南蹂,這兩個(gè)方法分別用于把 JavaScript 對象序列化為 JSON 字符串和把 JSON 字符串解析為原生 JavaScript 值。例如:
var book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book);
??這個(gè)例子使用 JSON.stringify() 把一個(gè) JavaScript 對象序列化為一個(gè) JSON 字符串念恍,然后將它保存在變量 jsonText 中六剥。
??默認(rèn)情況下,JSON.stringify() 輸出的 JSON 字符串不包含任何空格字符或縮進(jìn)峰伙,因此保存在 jsonText 中的字符串如下所示:
{"title":"Professional JavaScript","authors":["Nicholas C. Zakas"],"edition":3,"year":2011}
??在序列化 JavaScript 對象時(shí)疗疟,所有函數(shù)及原型成員都會(huì)被有意忽略,不體現(xiàn)在結(jié)果中瞳氓。此外策彤,值為 undefined 的任何屬性也都會(huì)被跳過。結(jié)果中最終都是值為有效 JSON 數(shù)據(jù)類型的實(shí)例屬性匣摘。
??將 JSON 字符串直接傳遞給 JSON.parse() 就可以得到相應(yīng)的 JavaScript 值店诗。例如,使用下列代碼就可以創(chuàng)建與 book 類似的對象:
var bookCopy = JSON.parse(jsonText);
??注意音榜,雖然 book 與 bookCopy 具有相同的屬性庞瘸,但它們是兩個(gè)獨(dú)立的、沒有任何關(guān)系的對象赠叼。
??如果傳給 JSON.parse() 的字符串不是有效的 JSON擦囊,該方法會(huì)拋出錯(cuò)誤违霞。
2.2、序列化選項(xiàng)
??實(shí)際上瞬场,JSON.stringify() 除了要序列化的 JavaScript 對象外买鸽,還可以接收另外兩個(gè)參數(shù),這兩個(gè)參數(shù)用于指定以不同的方式序列化 JavaScript 對象贯被。
??第一個(gè)參數(shù)是個(gè)過濾器眼五,可以是一個(gè)數(shù)組,也可以是一個(gè)函數(shù)刃榨;
??第二個(gè)參數(shù)是一個(gè)選項(xiàng)弹砚,表示是否在 JSON 字符串中保留縮進(jìn)双仍。單獨(dú)或組合使用這兩個(gè)參數(shù)枢希,可以更全面深入地控制 JSON 的序列化。
1. 過濾結(jié)果
??如果過濾器參數(shù)是數(shù)組朱沃,那么 JSON.stringify() 的結(jié)果中將只包含數(shù)組中列出的屬性苞轿。來看下面的例子。
var book = {
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book, ["title", "edition"]);
??JSON.stringify() 的第二個(gè)參數(shù)是一個(gè)數(shù)組逗物,其中包含兩個(gè)字符串:"title" 和 "edition"搬卒。這兩個(gè)屬性與將要序列化的對象中的屬性是對應(yīng)的,因此在返回的結(jié)果字符串中翎卓,就只會(huì)包含這兩個(gè)屬性:
{"title":"Professional JavaScript","edition":3}
??如果第二個(gè)參數(shù)是函數(shù)契邀,行為會(huì)稍有不同。傳入的函數(shù)接收兩個(gè)參數(shù)失暴,屬性(鍵)名和屬性值坯门。根據(jù)屬性(鍵)名可以知道應(yīng)該如何處理要序列化的對象中的屬性。屬性名只能是字符串逗扒,而在值并非鍵
值對兒結(jié)構(gòu)的值時(shí)古戴,鍵名可以是空字符串。
??為了改變序列化對象的結(jié)果矩肩,函數(shù)返回的值就是相應(yīng)鍵的值现恼。不過要注意,如果函數(shù)返回了undefined黍檩,那么相應(yīng)的屬性會(huì)被忽略叉袍。還是看一個(gè)例子吧。
var book = {
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
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;
}
});
??這里刽酱,函數(shù)過濾器根據(jù)傳入的鍵來決定結(jié)果喳逛。如果鍵為"authors",就將數(shù)組連接為一個(gè)字符串肛跌;如果鍵為"year"艺配,則將其值設(shè)置為 5000察郁;如果鍵為"edition",通過返回 undefined 刪除該屬性转唉。最后皮钠,一定要提供 default 項(xiàng),此時(shí)返回傳入的值赠法,以便其他值都能正常出現(xiàn)在結(jié)果中麦轰。
??實(shí)際上,第一次調(diào)用這個(gè)函數(shù)過濾器砖织,傳入的鍵是一個(gè)空字符串款侵,而值就是 book 對象。序列化后的 JSON 字符串如下所示:
{"title":"Professional JavaScript","authors":"Nicholas C. Zakas","year":5000}
??要序列化的對象中的每一個(gè)對象都要經(jīng)過過濾器侧纯,因此數(shù)組中的每個(gè)帶有這些屬性的對象經(jīng)過過濾之后新锈,每個(gè)對象都只會(huì)包含"title"、"authors"和"year"屬性眶熬。
??Firefox 3.5 和 3.6 對 JSON.stringify() 的實(shí)現(xiàn)有一個(gè) bug妹笆,在將函數(shù)作為該方法的第二個(gè)參數(shù)時(shí)這個(gè) bug 就會(huì)出現(xiàn),即這個(gè)函數(shù)只能作為過濾器:返回 undefined 意味著要跳過某個(gè)屬性娜氏,而返回其他任何值都會(huì)在結(jié)果中包含相應(yīng)的屬性拳缠。Firefox 4 修復(fù)了這個(gè) bug。
2. 字符串縮進(jìn)
??JSON.stringify() 方法的第三個(gè)參數(shù)用于控制結(jié)果中的縮進(jìn)和空白符贸弥。如果這個(gè)參數(shù)是一個(gè)數(shù)值窟坐,那它表示的是每個(gè)級(jí)別縮進(jìn)的空格數(shù)。例如绵疲,要在每個(gè)級(jí)別縮進(jìn) 4 個(gè)空格哲鸳,可以這樣寫代碼:
var book = {
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book, null, 4);
??保存在 jsonText 中的字符串如下所示:
{
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
"edition": 3,
"year": 2011
}
??JSON.stringify() 也在結(jié)果字符串中插入了換行符以提高可讀性。只要傳入有效的控制縮進(jìn)的參數(shù)值最岗,結(jié)果字符串就會(huì)包含換行符帕胆。(只縮進(jìn)而不換行意義不大。)最大縮進(jìn)空格數(shù)為 10般渡,所有大于 10 的值都會(huì)自動(dòng)轉(zhuǎn)換為 10懒豹。
??如果縮進(jìn)參數(shù)是一個(gè)字符串而非數(shù)值,則這個(gè)字符串將在 JSON 字符串中被用作縮進(jìn)字符(不再使用空格)驯用。在使用字符串的情況下脸秽,可以將縮進(jìn)字符設(shè)置為制表符,或者兩個(gè)短劃線之類的任意字符蝴乔。
var jsonText = JSON.stringify(book, null, " - -");
??這樣记餐,jsonText 中的字符串將變成如下所示:
{
--"title": "Professional JavaScript",
--"authors": [
----"Nicholas C. Zakas"
--],
--"edition": 3,
--"year": 2011
}
??縮進(jìn)字符串最長不能超過 10 個(gè)字符長。如果字符串長度超過了 10 個(gè)薇正,結(jié)果中將只出現(xiàn)前 10 個(gè)字符片酝。
3. toJSON() 方法
??有時(shí)候囚衔,JSON.stringify() 還是不能滿足對某些對象進(jìn)行自定義序列化的需求。在這些情況下雕沿,可以給對象定義 toJSON() 方法练湿,返回其自身的 JSON 數(shù)據(jù)格式。
??原生 Date 對象有一個(gè) toJSON() 方法审轮,能夠?qū)avaScript的 Date 對象自動(dòng)轉(zhuǎn)換成 ISO 8601 日期字符串(與在 Date 對象上調(diào)用toISOString() 的結(jié)果完全一樣)肥哎。
??可以為任何對象添加 toJSON() 方法,比如:
var book = {
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011,
toJSON: function(){
return this.title;
}
};
var jsonText = JSON.stringify(book);
??以上代碼在 book 對象上定義了一個(gè) toJSON() 方法疾渣,該方法返回圖書的書名篡诽。
??與 Date 對象類似,這個(gè)對象也將被序列化為一個(gè)簡單的字符串而非對象榴捡。
??可以讓 toJSON() 方法返回任何值杈女,它都能正常工作。比如薄疚,可以讓這個(gè)方法返回 undefined碧信,此時(shí)如果包含它的對象嵌入在另一個(gè)對象中,會(huì)導(dǎo)致它的值變成 null街夭,而如果它是頂級(jí)對象,結(jié)果就是 undefined躏筏。
??toJSON() 可以作為函數(shù)過濾器的補(bǔ)充板丽,因此理解序列化的內(nèi)部順序十分重要。假設(shè)把一個(gè)對象傳入 JSON.stringify()趁尼,序列化該對象的順序如下埃碱。
- (1) 如果存在 toJSON() 方法而且能通過它取得有效的值,則調(diào)用該方法酥泞。否則砚殿,返回對象本身。
- (2) 如果提供了第二個(gè)參數(shù)芝囤,應(yīng)用這個(gè)函數(shù)過濾器似炎。傳入函數(shù)過濾器的值是第 (1) 步返回的值。
- (3) 對第 (2) 步返回的每個(gè)值進(jìn)行相應(yīng)的序列化悯姊。
- (4) 如果提供了第三個(gè)參數(shù)羡藐,執(zhí)行相應(yīng)的格式化。
??無論是考慮定義 toJSON() 方法悯许,還是考慮使用函數(shù)過濾器仆嗦,亦或需要同時(shí)使用兩者,理解這個(gè)順序都是至關(guān)重要的先壕。
2.3瘩扼、解析選項(xiàng)
??JSON.parse() 方法也可以接收另一個(gè)參數(shù)谆甜,該參數(shù)是一個(gè)函數(shù),將在每個(gè)鍵值對兒上調(diào)用集绰。
??為了區(qū)別 JSON.stringify() 接收的替換(過濾)函數(shù)(replacer)店印,這個(gè)函數(shù)被稱為還原函數(shù)(reviver),但實(shí)際上這兩個(gè)函數(shù)的簽名是相同的——它們都接收兩個(gè)參數(shù)倒慧,一個(gè)鍵和一個(gè)值按摘,而且都需要返回一個(gè)值。
??如果還原函數(shù)返回 undefined纫谅,則表示要從結(jié)果中刪除相應(yīng)的鍵炫贤;如果返回其他值,則將該值插入到結(jié)果中付秕。在將日期字符串轉(zhuǎn)換為 Date 對象時(shí)兰珍,經(jīng)常要用到還原函數(shù)。例如:
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;
}
});
alert(bookCopy.releaseDate.getFullYear());
??以上代碼先是為 book 對象新增了一個(gè) releaseDate 屬性询吴,該屬性保存著一個(gè) Date 對象掠河。這個(gè)對象在經(jīng)過序列化之后變成了有效的 JSON 字符串,然后經(jīng)過解析又在 bookCopy 中還原為一個(gè) Date 對象猛计。還原函數(shù)在遇到"releaseDate"鍵時(shí)唠摹,會(huì)基于相應(yīng)的值創(chuàng)建一個(gè)新的 Date 對象。結(jié)果就是 bookCopy.releaseDate 屬性中會(huì)保存一個(gè) Date 對象奉瘤。正因?yàn)槿绱斯蠢拍芑谶@個(gè)對象調(diào)用 getFullYear() 方法。
小結(jié)
??JSON 是一個(gè)輕量級(jí)的數(shù)據(jù)格式盗温,可以簡化表示復(fù)雜數(shù)據(jù)結(jié)構(gòu)的工作量藕赞。JSON 使用 JavaScript 語法的子集表示對象、數(shù)組卖局、字符串斧蜕、數(shù)值、布爾值和 null砚偶。
??即使 XML 也能表示同樣復(fù)雜的數(shù)據(jù)結(jié)果批销,但 JSON 沒有那么煩瑣,而且在 JavaScript 中使用更便利蟹演。
??ECMAScript 5 定義了一個(gè)原生的 JSON 對象风钻,可以用來將對象序列化為 JSON 字符串或者將 JSON 數(shù)據(jù)解析為 JavaScript 對象。
??JSON.stringify() 和 JSON.parse() 方法分別用來實(shí)現(xiàn)上述兩項(xiàng)功能酒请。
??這兩個(gè)方法都有一些選項(xiàng)骡技,通過它們可以改變過濾的方式,或者改變序列化的過程。
??原生的 JSON 對象也得到了很多瀏覽器的支持布朦,比如 IE8+囤萤、Firefox 3.5+、Safari 4+是趴、Opera 10.5 和 Chrome涛舍。