JSON簡(jiǎn)介
摘要
XML——這種用于表示客戶端與服務(wù)器間數(shù)據(jù)交換有效負(fù)載的格式,幾乎已經(jīng)成了Web services的同義詞。然而,由于Ajax和REST技術(shù)的出現(xiàn)影響了應(yīng)用程序架構(gòu),這迫使人們開(kāi)始尋求`XML的替代品,如:JavaScript Object Notation(JSON)。
JSON 作為一種更輕、更友好的 Web services客戶端的格式(多采用瀏覽器的形式或訪問(wèn) REST風(fēng)格 Web服務(wù)的Ajax應(yīng)用程序的形式)引起了 Web 服務(wù)供應(yīng)商的注意。
本文將闡述JSON在Web services設(shè)計(jì)中備受推崇的原因,以及它作為XML替代方案的主要優(yōu)勢(shì)和局限性。文中還會(huì)深入探討:隨著相應(yīng)的Web 服務(wù)客戶端選擇使用JSON,如何才能便捷地在Java Web services中生成JSON輸出。
XML的十字路口: 瀏覽器和 Ajax
XML設(shè)計(jì)原理已經(jīng)發(fā)布了將近十年。時(shí)至今日,這種標(biāo)記語(yǔ)言已經(jīng)在廣闊的軟件應(yīng)用領(lǐng)域中占據(jù)了主導(dǎo)地位。從Java、.NET等主流平臺(tái)中的配置和部署描述符到應(yīng)用集成場(chǎng)景中更復(fù)雜的應(yīng)用,XML與生俱來(lái)的語(yǔ)言無(wú)關(guān)性使之在軟件架構(gòu)師心目中占據(jù)著獨(dú)特的地位。但即便最著名的XML權(quán)威也不得不承認(rèn):在某些環(huán)境中,XML的使用已經(jīng)超出了它自身能力的極限。
圍繞Ajax原理構(gòu)建的那些Web應(yīng)用程序最能說(shuō)明XML的生存能力,從這一點(diǎn)來(lái)看,一種新的有效負(fù)載格式的發(fā)展壯大也得益于XML。這種新的有效負(fù)載格式就是JavaScript Object Notation (JSON)。在探索這種新的標(biāo)記語(yǔ)言的復(fù)雜性之前,首先來(lái)分析一下在這種獨(dú)特的設(shè)計(jì)形式中,XML具有哪些局限性。
Ajax建立了一個(gè)用于從遠(yuǎn)程Web services發(fā)送和接收數(shù)據(jù)的獨(dú)立信道,從而允許Web程序執(zhí)行信道外(out-of-band)客戶端/服務(wù)器調(diào)用。通俗地說(shuō),Ajax程序中的更新和導(dǎo)航序列在典型的客戶端/服務(wù)器環(huán)境之外完成,在后臺(tái)(即信道外)接受到信息后,必須進(jìn)行一次完整的屏幕刷新。更多背景信息,請(qǐng)參閱David Teare的 Ajax簡(jiǎn)介(Dev2Dev)。
這些應(yīng)用程序更新通常是通過(guò)REST風(fēng)格(RESTful)Web services獲得的,一旦被用戶的瀏覽器接收到,就需要整合到HTML頁(yè)面的總體布局之中,這正是XML發(fā)揮強(qiáng)大力量的場(chǎng)合。盡管近年來(lái),腳本語(yǔ)言支持和插件支持已使大多數(shù)主流瀏覽器的功能得到了強(qiáng)化,但許多編程任務(wù)依然難于開(kāi)展,其中之一就是操縱或處理文本,這通常是使用DOM實(shí)現(xiàn)的。
采用DOM的復(fù)雜性源于其基于函數(shù)的根,這使得對(duì)數(shù)據(jù)樹(shù)的簡(jiǎn)單修改或訪問(wèn)都需要進(jìn)行無(wú)數(shù)次方法調(diào)用。此外,眾所周知,DOM在各種瀏覽器中的實(shí)現(xiàn)細(xì)節(jié)不盡相同,這一過(guò)程將帶來(lái)極為復(fù)雜的編程模式,其跨瀏覽器兼容性出現(xiàn)問(wèn)題的可能性極大。接下來(lái)的問(wèn)題顯而易見(jiàn),那就是:如何使一種標(biāo)記語(yǔ)言輕松集成到HTML頁(yè)面中以滿足Ajax的要求?
問(wèn)題的答案就是:利用所有主流瀏覽器中的一種通用組件——JavaScript引擎。XML需要使用DOM之類(lèi)的機(jī)制來(lái)訪問(wèn)數(shù)據(jù)并將數(shù)據(jù)整合到布局之中,采用這種方法,我們不再使用像XML這樣的格式來(lái)交付Ajax更新,而是采用一種更為簡(jiǎn)單直觀的方式,采用JavaScript引擎自然匹配的格式——也就是JSON。
既然已經(jīng)明確了JSON與XML和Ajax之間的關(guān)系,下面將進(jìn)一步探討JSON背后的技術(shù)細(xì)節(jié)。
JSON剖析:優(yōu)點(diǎn)和不足
對(duì)于JSON,首先要明白JSON和XML一樣也是一種簡(jiǎn)單文本格式。相對(duì)于XML,它更加易讀、更便于肉眼檢查。在語(yǔ)法的層面上,JSON與其他格式的區(qū)別是在于分隔數(shù)據(jù)的字符,JSON中的分隔符限于單引號(hào)、小括號(hào)、中括號(hào)、大括號(hào)、冒號(hào)和逗號(hào)。下圖是一個(gè)JSON有效負(fù)載:
{"addressbook": {"name": "Mary Lebow",
"address": {
"street": "5 Main Street"
"city": "San Diego, CA",
"zip": 91912,
},
"phoneNumbers": [
"619 332-3452",
"664 223-4667"
]
}
}
將上面的JSON有效負(fù)載用XML改寫(xiě),如下:
<addressbook>
<name>Mary Lebow</name>
<address>
<street>5 Main Street</street>
<city zip="91912"> San Diego, CA </city>
<phoneNumbers>
<phone>619 332-3452</phone>
<phone>664 223-4667</phone>
</phoneNumbers>
</address>
</addressbook>
是不是很相似?但它們并不相同。下面將詳細(xì)闡述采用JSON句法的優(yōu)點(diǎn)和不足。
優(yōu)點(diǎn)
乍看上去,使用JSON的數(shù)據(jù)分隔符的優(yōu)點(diǎn)可能并不那么明顯,但存在一個(gè)根本性的緣由:它們簡(jiǎn)化了數(shù)據(jù)訪問(wèn)。使用這些數(shù)據(jù)分隔符時(shí), JavaScript引擎對(duì)數(shù)據(jù)結(jié)構(gòu)(如字符串、數(shù)組、對(duì)象)的內(nèi)部表示恰好與這些符號(hào)相同。
這將開(kāi)創(chuàng)一條比DOM技術(shù)更為便捷的數(shù)據(jù)訪問(wèn)途徑。下面列舉幾個(gè)JavaScript代碼片段來(lái)說(shuō)明這一過(guò)程,這些代碼片段會(huì)訪問(wèn)先前的JSON代碼片段中的信息:
- 訪問(wèn)JSON中的名稱: addressbook.name
- 訪問(wèn)JSON中的地址: addressbook.address.street
- 訪問(wèn)JSON中的電話號(hào)碼第一位:addressbook.address.phoneNumbers[0]
如果您具備DOM編程經(jīng)驗(yàn),就能很快地看出區(qū)別;新手可以參看 Document Object Model 的這一外部資源,這里提供了關(guān)于數(shù)據(jù)導(dǎo)航的實(shí)例。
JSON的另一個(gè)優(yōu)點(diǎn)是它的非冗長(zhǎng)性。在XML中,打開(kāi)和關(guān)閉標(biāo)記是必需的,這樣才能滿足標(biāo)記的依從性;而在JSON中,所有這些要求只需通過(guò)一個(gè)簡(jiǎn)單的括號(hào)即可滿足。在包含有數(shù)以百計(jì)字段的數(shù)據(jù)交換中,傳統(tǒng)的XML標(biāo)記將會(huì)延長(zhǎng)數(shù)據(jù)交換時(shí)間。目前還沒(méi)有正式的研究表明JSON比XML有更高的線上傳輸效率;人們只是通過(guò)簡(jiǎn)單的字節(jié)數(shù)比較發(fā)現(xiàn),對(duì)于等效的JSON和XML有效負(fù)載,前者總是小于后者。至于它們之間的差距有多大,特別是在新的XML壓縮格式下它們的差距有多大,有待進(jìn)一步的研究。
此外,JSON受到了擅長(zhǎng)不同編程語(yǔ)言的開(kāi)發(fā)人員的青睞。這是因?yàn)闊o(wú)論在Haskell中或 Lisp中,還是在更為主流的C#和PHP中,開(kāi)發(fā)都可以方便地生成JSON(詳見(jiàn) 參考資料)。
不足
和許多好東西都具有兩面性一樣,JSON的非冗長(zhǎng)性也不例外,為此JSON丟失了XML具有的一些特性。命名空間允許不同上下文中的相同的信息段彼此混合,然而,顯然在JSON中已經(jīng)找不到了命名空間。JSON與XML的另一個(gè)差別是屬性的差異,由于JSON采用冒號(hào)賦值,這將導(dǎo)致當(dāng)XML轉(zhuǎn)化為JSON時(shí),在標(biāo)識(shí)符(XML CDATA)與實(shí)際屬性值之間很難區(qū)分誰(shuí)應(yīng)該被當(dāng)作文本考慮。
另外,JSON片段的創(chuàng)建和驗(yàn)證過(guò)程比一般的XML稍顯復(fù)雜。從這一點(diǎn)來(lái)看,XML在開(kāi)發(fā)工具方面領(lǐng)先于JSON。盡管如此,為了消除您對(duì)這一領(lǐng)域可能存在的困惑,下節(jié)將介紹一些最為成熟的JSON開(kāi)發(fā)。
從Web services生成JSON輸出
既然JSON的首要目標(biāo)是來(lái)自瀏覽器的信道外請(qǐng)求,那么我們選擇REST風(fēng)格(RESTful)Web服務(wù)來(lái)生成這些數(shù)據(jù)。除了用典型業(yè)務(wù)邏輯探究Web服務(wù)之外,還將采用特定的API把本地Java結(jié)構(gòu)轉(zhuǎn)化為JSON格式(詳見(jiàn) 參考資料)。首先,下面的Java代碼用來(lái)操縱Address對(duì)象:
// Create addressbook data structure
SortedMap addressBook = new TreeMap();
// Create new address entries and place in Map
// (See download for Address POJO structure)
Address maryLebow = new Address("5 Main Street","San Diego, CA",91912,"619-332-3452","664-223-4667");
addressBook.put("Mary Lebow",maryLebow);
Address amySmith = new Address("25 H Street","Los Angeles, CA",95212,"660-332-3452","541-223-4667");
addressBook.put("Sally May",amySmith);
Address johnKim = new Address("2343 Sugarland Drive","Houston, TX",55212,"554-332-3412","461-223-4667");
addressBook.put("John Kim",johnKim);
Address richardThorn = new Address("14 68th Street","New York, NY",,12452,"212-132-6182","161-923-4001");
addressBook.put("Richard Thorn",richardThorn);
該Java結(jié)構(gòu)在哪里生成并不重要(可能是在JSP、Servlet、EJB或POJO中生成),重要的是,在REST風(fēng)格Web 服務(wù)中有權(quán)使用這些數(shù)據(jù)。如下示:
// Define placeholder for JSON response
String result = new String();
// Get parameter (if any) passed into application
String from = request.getParameter("from");
String to = request.getParameter("to");
try {
// Check for parameters, if passed filter address book
if(from != null && to != null) {
// Filter address book by initial
addressBook = addressBook.subMap(from,to);
}
// Prepare the convert addressBook Map to JSON array
// Array used to place numerous address entries
JSONArray jsonAddressBook = new JSONArray();
// Iterate over filtered addressBook entries
for (Iterator iter = addressBook.entrySet().iterator(); iter.hasNext();) {
// Get entry for current iteration
Map.Entry entry = (Map.Entry)iter.next();
String key = (String)entry.getKey();
Address addressValue = (Address)entry.getValue();
// Place entry with key value assigned to "name"
JSONObject jsonResult = new JSONObject();
jsonResult.put("name",key);
// Get and create address structure corresponding to each key
// appending address entry in JSON format to result
String streetText = addressValue.getStreet();
String cityText = addressValue.getCity();
int zipText = addressValue.getZip();
JSONObject jsonAddress = new JSONObject();
jsonAddress.append("street",streetText);
jsonAddress.append("city",cityText);
jsonAddress.append("zip",zipText);
jsonResult.put("address",jsonAddress);
// Get and create telephone structure corresponding to each key
// appending telephone entries in JSON format to result
String telText = addressValue.getTel();
String telTwoText = addressValue.getTelTwo();
JSONArray jsonTelephones = new JSONArray();
jsonTelephones.put(telText);
jsonTelephones.put(telTwoText);
jsonResult.put("phoneNumbers",jsonTelephones);
// Place JSON address entry in global jsonAddressBook
jsonAddressBook.put(jsonResult);
} // end loop over address book
// Assign JSON address book to result String
result = new JSONObject().put("addressbook",jsonAddressBook).toString();
} catch (Exception e) {
// Error occurred
}
為了便于說(shuō)明,我們已將這段代碼將置入JSP(restservice.jsp)中。如果它真是一段程序,那么類(lèi)似這樣的代碼也會(huì)出現(xiàn)在servlet或helper類(lèi)中。 REST風(fēng)格Web服務(wù)首先提取兩個(gè)通過(guò)URL請(qǐng)求傳遞給它的輸入?yún)?shù),根據(jù)這些值過(guò)濾現(xiàn)有的地址簿以適應(yīng)請(qǐng)求。過(guò)濾過(guò)地址簿后,即可開(kāi)始循環(huán)檢查Java映射中的每個(gè)條目。
您會(huì)注意到,在循環(huán)內(nèi)部,json.org API被廣泛用于將本地Java格式轉(zhuǎn)化為JSON字符串。雖然僅使用了少量類(lèi)(即JSONArray和JSONObject),但API提供的轉(zhuǎn)換方法相當(dāng)廣泛,甚至能將XML結(jié)構(gòu)轉(zhuǎn)換成JSON輸出。但回到我們的Web服務(wù),一旦循環(huán)遍歷了所有條目,那么變量“result”會(huì)包含準(zhǔn)備返回給請(qǐng)求方的地址簿的JSON同等部分。
既然已經(jīng)生成了JSON輸出,下面來(lái)看看等式的另一邊:瀏覽器應(yīng)用程序中JSON有效負(fù)載的使用。
JSON有效負(fù)載的使用
作為基于瀏覽器的客戶端,我們的設(shè)計(jì)中大部分工作都是在HTML、JavaScript加上附加的JavaScript 框架下完成的。例如利用Prototype庫(kù)輕松創(chuàng)建跨瀏覽器樣式的Ajax調(diào)用。下面的清單包含了我們的應(yīng)用程序的第一部分,以及相應(yīng)的JavaScript函數(shù)。
<html>
<head>
<title> JSON Address Book </title>
<script type="text/javascript" src="prototype-1.4.0.js"></script>
<script type="text/javascript">
// Method invoked when user changes letter range
function searchAddressBook() {
// Select values from HTML select lists
var fromLetter = $F('fromLetter');
var toLetter = = $F('toLetter');
// Prepare parameters to send into REST web service
var pars = 'from=' + fromLetter + '&to=' + toLetter;
// Define REST web service URL
var url = 'restservice.jsp';
// Make web service Ajax request via prototype helper,
// upon response, call showResponse method
new Ajax.Request( url, { method: 'get', parameters: pars,
onComplete: showResponse });
}
</script>
</head>
首先導(dǎo)入了prototype庫(kù),該庫(kù)用于促進(jìn)對(duì)REST風(fēng)格Web服務(wù)的Ajax調(diào)用。接下來(lái)是searchAddressBook()函數(shù),當(dāng)用戶修改其下所示的HTML選擇列表時(shí),將會(huì)觸發(fā)此函數(shù)。該函數(shù)被觸發(fā)后,用戶將會(huì)獲得HTML選擇列表中已選中的選項(xiàng),并將其放入兩個(gè)用于過(guò)濾地址簿的變量中,隨后定義一個(gè)指向REST風(fēng)格服務(wù)URL restservice.jsp的附加變量。
此方法中還包括借助原型函數(shù)new Ajax.Request( url, { method: 'get', parameters: pars, onComplete: showResponse }); 的實(shí)際Ajax Web服務(wù)調(diào)用;表明了對(duì)相關(guān)URL的一個(gè)請(qǐng)求,其請(qǐng)求參數(shù)包含在pars中;最后一旦Ajax請(qǐng)求終止,即執(zhí)行showResponse()。
下面以showResponse()為例說(shuō)明用于評(píng)估JSON有效負(fù)載并將其放入HTML主體布局環(huán)境中的的必要代碼。
// Method invoked when page receives Ajax response from REST web service
function showResponse(originalRequest) {
// Get JSON values
jsonRaw = originalRequest.responseText;
// Eval JSON response into variable
jsonContent = eval("(" + jsonRaw + ")");
// Create place holder for final response
finalResponse = "<b>" + jsonContent.addressbook.length +
" matches found in range</b><br/>";
// Loop over address book length.
for (i = 0; i < jsonContent.addressbook.length; i++) {
finalResponse += "<hr/>";
finalResponse += "<i>Name:</i> " + jsonContent.addressbook[i].name + "<br/>";
finalResponse += "<i>Address:</i> " + jsonContent.addressbook[i].address.street + " -- " +
jsonContent.addressbook[i].address.city + "," +
jsonContent.addressbook[i].address.zip + ".<br/>";
finalResponse += "<i>Telephone numbers:</i> " + jsonContent.addressbook[i].phoneNumbers[0] + " & " +
jsonContent.addressbook[i].phoneNumbers[1] + ".";
}
// Place formatted finalResponse in div element
document.getElementById("addressBookResults").innerHTML = finalResponse;
}
此方法的輸入?yún)?shù)是REST風(fēng)格Web服務(wù)在調(diào)用時(shí)返回的響應(yīng)。既然預(yù)先已經(jīng)知道需要處理JSON字符串,那么可以利用JavaScript eval()函數(shù),將這個(gè)JSON字符串放入內(nèi)存,并允許數(shù)據(jù)訪問(wèn),正是這樣的簡(jiǎn)便性促使開(kāi)發(fā)人員使用JSON。完全不需要進(jìn)行解析,一個(gè)簡(jiǎn)單的eval()即可得到JavaScript結(jié)構(gòu),我們可以像操縱其他任何JavaScript結(jié)構(gòu)一樣地去操縱它。
一旦JSON響應(yīng)經(jīng)過(guò)eval處理,將創(chuàng)建一個(gè)JavaScript循環(huán)來(lái)提取每個(gè)地址條目,并將各個(gè)匹配項(xiàng)放入一個(gè)名為finalResponse的容器變量中。而這個(gè)容器變量本身包含所有必要的格式,用于在頁(yè)面布局中顯示最終地址簿。循環(huán)結(jié)束時(shí),匹配項(xiàng)也通過(guò)document.getElementById("addressBookResults").innerHTML放置完畢。
最后,為了保持完整,頁(yè)面的實(shí)際布局由這些代碼組成:
<body>
<h4 style="text-align:left">Request address book matches:</h4>
<table style="text-align:left" cellpadding="15"><tr><td valign="top">From:<br/>
<select id="fromLetter" size="15" onchange="searchAddressBook()">
<option>A</option>
...
<option>Z</option>
</select>
</td><td valign="top">To:<br/>
<select id="toLetter" size="15" onchange="searchAddressBook()">
<option>A</option>
...
<option>Z</option>
</select>
</td><td valign="top">
<h5> Results </h5>
<div style="text-align:left" id="addressBookResults">Please select range</div>
</td></tr>
</table>
</body>
上面的代碼清單中最值得一提的是HTML選擇列表,因?yàn)樾薷挠|發(fā)器Java程序需要調(diào)用信道外Ajax請(qǐng)求。其次,<div>元素就是放置格式化后的JSON響應(yīng)的地方。
JSON適合您嗎
就像在軟件設(shè)計(jì)中編程語(yǔ)言的選擇一樣,JSON的選擇與否取決于您自身的需求。如果Web services使用者將在傳統(tǒng)、功能完備的編程環(huán)境(如Java 、.NET、PHP、Ruby等)中創(chuàng)建,那么完全可以不使用JSON。給定大多數(shù)編程語(yǔ)言環(huán)境的無(wú)限制能力可提供完整的配置控制權(quán)(更不必說(shuō)對(duì)定制庫(kù)、分析器或helper類(lèi)的訪問(wèn)),那么JSON與XML及其他Web services有效載荷之間的差別可以忽略不計(jì)。
反之,如果Web services使用者被限制在瀏覽器環(huán)境之外,那么JSON是值得認(rèn)真考慮的對(duì)象。 在瀏覽器中使用Web services并非興趣使然,而是實(shí)際業(yè)務(wù)需求。如果這時(shí)需要一個(gè)加載數(shù)據(jù)時(shí)不會(huì)出現(xiàn)延遲/刷新的“漂亮的Web 2.0界面”, 就不得不在瀏覽器中嵌入Ajax和Web services技術(shù)。
在這種情況下,您不僅受限于通過(guò)網(wǎng)絡(luò)訪問(wèn)處理環(huán)境,而且還會(huì)受到隨機(jī)用戶的限制,迫使經(jīng)驗(yàn)豐富的開(kāi)發(fā)人員用最普遍的工具在瀏覽器中處理文本,例如:前述的DOM,與訪問(wèn)JSON樹(shù)相比,DOM使用起來(lái)非常困難。
示例代碼
您可下載與本文相關(guān)的代碼。
安裝之前先解壓下載得到的文件,將addressbook.html,prototype-1.4.0.js和restservice.jsp放入任意程序的目錄下。將內(nèi)含的json.jar復(fù)制到所選程序的/WEB-INF/lib目錄下。訪問(wèn)<yourhost>/<yourappdir>/addressbook.html,并在HTML列表中進(jìn)行選擇。一切就緒,可以運(yùn)行JSON了!
結(jié)束語(yǔ)
盡管 “Ajax”中的“x”代表XML,Web services也通過(guò)堅(jiān)持使用XML格式而成為主流,但這并不意味著這種方式無(wú)懈可擊。在文本處理方面,XML在Ajax程序的應(yīng)用中已經(jīng)暴露出一些缺點(diǎn)。在這種情形下,JSON逐漸成為引人注目的XML替代方案。
通過(guò)對(duì)JSON語(yǔ)法優(yōu)缺點(diǎn)的論述,以及對(duì)如何從REST風(fēng)格Web services創(chuàng)建JSON輸出、如何將其嵌入Web頁(yè)面布局等問(wèn)題的介紹,您現(xiàn)在應(yīng)該能夠?yàn)樽罱K用戶提供支持JSON的Web services,接觸當(dāng)前提供的大量利用這一極具前途的格式的Web services。
參考資料
- Ajax簡(jiǎn)介(David Teare,Dev2Dev,2005年8月)——介紹Ajax的相關(guān)技術(shù)背景
- JSON in Java ——介紹使用Java API將本地Java結(jié)構(gòu)轉(zhuǎn)換成JSON輸出
- Prototype ——介紹用于創(chuàng)建Ajax應(yīng)用程序的JavaScript框架
- JSON.org ——JSON主頁(yè),其中包含可生成JSON的各種語(yǔ)言庫(kù)的資源和鏈接
作者簡(jiǎn)介 |
|
Daniel Rubio 是一名軟件顧問(wèn),具有十余年的企業(yè)軟件開(kāi)發(fā)經(jīng)驗(yàn)。近期,他創(chuàng)辦了Mashup Soft公司,開(kāi)始專攻為Mashup使用Web services的有關(guān)方面。 |