JSON 必知必會書籍學(xué)習(xí)記錄筆記,想深入一下JSON Hijacking 漏洞,所以就打算找一本JSON的書籍來讀一遍,打撈自己的基礎(chǔ),于是就選了這本書來學(xué)習(xí),以后這種讀書筆記的方式可能還會堅持下去,這樣可以逼自己養(yǎng)成閱讀的習(xí)慣,順便又寫了博客,何樂不為呢?
作者: [美] Lindsay Bassett
譯者: 魏嘉汛
出版年: 2016-5
頁數(shù): 128
定價: 35.00元
裝幀: 平裝
叢書: 圖靈程序設(shè)計叢書·Web開發(fā)系列
ISBN: 9787115422071
JSON 是一種輕量的數(shù)據(jù)格式,在各種地方傳遞數(shù)據(jù)。
數(shù)據(jù)交換格式是一種在不同平臺間傳遞數(shù)據(jù)的文本格式。除 JSON 外,常用的還有XML數(shù)據(jù)交換格式。
JSON 的全稱是 JavaScript Object Notation(JavaScript 對象表示法),JSON 雖然源于 JavaScript 的一個子集,但是對JS不熟悉的話問題也不大,因為JSON現(xiàn)在也被廣泛地應(yīng)用于其他的編程語言中。
JSON 中并不會涉及 JavaScript 對象 字面量中的函數(shù)。JSON 所基于的 JavaScript 對象字面量單純指對象字面量及 其屬性的語法表示。這種屬性表示方法也就是通過名稱 - 值對來實現(xiàn)的。
使用JSON描述我現(xiàn)在穿的鞋子
{ "brand": "Crocs", "color": "pink", "size": 9, "hasLaces": false}
名稱 - 值對的概念非常流行。它們也有別的名字,像鍵 - 值對、屬性 - 值對或字段 - 值對等
JSON 中使用冒號:
來分隔名稱和值。名稱始終在左側(cè),值始終在右側(cè)
JSON中的 名稱 必需要用雙引號包裹,為了代碼友好和可移植性,名稱 - 值 中盡量避免使用空格或特殊字符。
屬性名可以包含下劃線或數(shù)字,但是在使用多個單詞的時候,推薦使用駝峰命名法,比如下面這樣:
"myAnimal": "cat"
值是字符串時,必須使用雙引號包裹。
而在 JSON 中,還 有數(shù)字、布爾值、數(shù)組、對象、null 等其他數(shù)據(jù)類型,這些都不應(yīng)被雙引號 包裹,這些格式下面的章節(jié)會講解。下面字符提供的是告訴機器如何讀取數(shù)據(jù)的指令:
{
開始讀取對象}
結(jié)束讀取對象[
開始讀取數(shù)組]
結(jié)束讀取數(shù)組:
在名稱 - 值對中分隔名稱和值,
分隔對象中的名稱 - 值對 或者 分隔數(shù)組中的值 也可以認(rèn)為是 一個新部分的開始這不是合法的 JSON
{ 'title': 'This is my title.', 'body': 'This is the body.' }
在 JSON 中, 名稱 必需要用雙引號包裹
合法的 JSON
{ "title": "This is my title.", "body": "This is the body." }
很多IDE自帶了JSON語法驗證,我們敲錯的時候回報錯,本書中還提到了一些在線的工具地址,用戶JSON格式校驗和代碼高亮美化,這里國光我就不推薦了。VSCode里面都集成了,用起來更舒服一點。
JSON的文件擴展名:.json
國外人寫書也太詳細(xì)了吧,這個都要寫出來
傳遞數(shù)據(jù)時,需要提前告知接收方數(shù)據(jù)是什么類型,也就是我們常用的 MIME 類型。
JSON 的 MIME 類型是 application/json
一個比較全面的MIME表:http://www.iana.org/assignments/media-types/
數(shù)據(jù)類型是學(xué)習(xí)各種編程語言老生常談的問題,語言是想通的,有其他編程語言基礎(chǔ)的話,這里學(xué)起來就會很輕松。
不同類型的數(shù) 據(jù)有著不同的操作途徑,可以讓兩個數(shù)相乘,但是不能讓一個單詞和一個 數(shù)相乘。
在計算機科學(xué)中,有一種數(shù)據(jù)類型被稱為原始數(shù)據(jù)類型,是最基本的一種類型:
數(shù)字(如 5 或 5.09)
– 整型
– 浮點數(shù)
– 定點數(shù)
字符和字符串(如“a”“A”或“apple”)
布爾類型(即真或假)
除了原始數(shù)據(jù)類型,大多數(shù)編程語言中還有許多其他的類型,它們嘗嘗被稱為復(fù)合數(shù)據(jù)類型。
枚舉數(shù)據(jù)類型是編程語言中常見的復(fù)合數(shù)據(jù)類型之一,是一個可以枚舉的數(shù)據(jù)結(jié)構(gòu)。
枚舉類型 Demo
["witty", "charming", "brave", "bold"]
另一種復(fù)合數(shù)據(jù)類型是對象數(shù)據(jù)類型,這個之前我們已經(jīng)見過它了:
{ "brand": "Crocs", "color": "pink", "size": 9, "hasLaces": false}
JSON的數(shù)據(jù)類型 在其他語言中的支持度也很高。
JSON 中的數(shù)據(jù)類型包括:
JSON 本身就是對象,也就是一 個被花括號包裹的名稱 - 值對的列表。
下面代碼展示了如何用嵌套對象來描述一個人:
{ "person": { "name": "Lindsay Bassett", "heightInInches": 66, "head": { "hair": { "color": "light blond", "length": "short", "style": "A-line" }, "eyes": "green" } } }
JSON 中的字符串可以由任何 Unicode 字符構(gòu)成,字符串的兩邊必須被雙引號包裹。當(dāng)值里面出現(xiàn)雙引號的時候,我們需要在雙引號前面加上一個反斜線字符來對其轉(zhuǎn)義:
{ "promo": "Say \"Bob's the best!\" at checkout for free 8oz bag of kibble."}
此外 反斜線本身也需要轉(zhuǎn)義。
在這段代碼中使用反斜線會報錯
{ "location": "C:\Program Files"}
反斜線需要另一個反斜線來轉(zhuǎn)義
{ "location": "C:\\Program Files" }
除了雙引號和反斜線,還需要轉(zhuǎn)義以下字符:
\/
(正斜線)\b
(退格符)\f
(換頁符)\t
(制表符)\n
(換行符)\r
(回車符)\u
后面跟十六進(jìn)制字符(如笑臉表情 \u263A)JSON 中的數(shù)字表示
{ "widgetInventory": 289, "sadSavingsAccount": 22.59, "seattleLatitude": 47.606209, "seattleLongitude": -122.332071, "earthsMass": 5.97219e+24}
JSON 中的數(shù)字可以是整數(shù)、小數(shù)、負(fù)數(shù)或者指數(shù)。
在一些編程語言中,true 的字面值可能用 1 來表示,false 用 0 來表示,有時候字面值也可能是大寫或小寫的單詞,比如 true 或 TRUE,false 或 FALSE。
但是在JSON中僅使用小寫形式:true 或 false,任何其他 形式的寫法都會報錯。
{ "toastWithBreakfast": false, "breadWithLunch": true }
在編程中,null 就用來表 示 0、一無所有、不存在等意思,而不用數(shù)字來表示。
在 JavaScript 中,undefined 是在嘗試獲取一些不存在的對象或變量時返回的結(jié)果,undefined 與那些聲 明的名稱和值都不存在的對象或變量有關(guān),而 null 則僅與對象或變量的值 有關(guān)。null 是一個表示“沒有值”的值。在 JSON 中,null 必須使用小寫 形式。
{ "eggCarton": [ "egg", "egg", "egg", "egg", "egg", 1, 2, 3 ] }
數(shù)組 始終應(yīng)被方括號[]
包裹。在數(shù)組中,可以看到一個列表,列表項之間 用,
隔開,列表中的值可以是任何合法的 JSON 數(shù)據(jù)類型(字符串、數(shù) 字、對象、布爾值、數(shù)組以及 null),在JSON中的值可以混合使用不同的數(shù)據(jù)類型。
數(shù)據(jù)通過互聯(lián)網(wǎng)或其他網(wǎng)絡(luò)傳輸?shù)浇邮辗降倪^程中可能會出錯,所以就需要對數(shù)據(jù)進(jìn)行驗證。早在 JSON 出現(xiàn)以前,這樣的情況就已經(jīng)存在于數(shù)據(jù)交換之中。幸運的是, 技術(shù)從業(yè)者都是擅長解決問題的人,Schema(意為模式)這一概念也隨之 誕生。
數(shù)據(jù)驗證很重要,生活中很多場景都需要數(shù)據(jù)驗證,JSON也是如此,以保證數(shù)據(jù)符合要求。
盡管 JSON 已經(jīng)相當(dāng)成熟,但 JSON Schema 仍在開發(fā)之中。
JSON Schema 使用 JSON 來書寫,所以僅需幾步就能掌握它。
要 在 JSON 第一個名稱 - 值對中,聲明其為一個 schema 文件:
聲明的名稱必須為 “$schema”,值必須為所用草擬版本的鏈接
{ "$schema": "http://json-schema.org/draft-04/schema#" }
第二個名稱 - 值對應(yīng)該是 JSON Schema 文件的標(biāo)題
表示一只貓的 JSON Schema 文件格式
{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Cat"}
在 JSON Schema 文件的第三個名稱值對中,要定義需要在 JSON 中包含的 屬性?!眕roperties” 的值實質(zhì)上是我們想要的 JSON 的名稱 - 值對的骨架。 我們沒有使用字面量,而是使用了一個對象來定義數(shù)據(jù)類型,并有選擇地 進(jìn)行描述:
{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Cat", "properties": { "name": { "type": "string" }, "age": { "type": "number", "description": "Your cat's age in years." }, "declawed": { "type": "boolean" } } }
接下來就能驗證 JSON 是否符合 JSON Schema
這個 JSON 符合針對貓定義的 JSON Schema
{ "name": "Fluffy", "age": 2, "declawed": false}
可以驗證表示“Fluffy”這只貓的 JSON 中的 name、age 和 declawed 是否都有正確類型的值。
此外還可以驗證
這里開發(fā)相關(guān),國光我這里就不重點說這塊內(nèi)容了,感興趣的話可以在原書本中找到答案。
JSON 本身不會構(gòu)成什么威脅,畢竟它只是一種數(shù)據(jù)交換格式。它不過是一 種數(shù)據(jù)文件,或者說數(shù)據(jù)流。真正會產(chǎn)生安全問題的是 JSON 的使用。本 章重點討論在 Web 中使用 JSON 時最常見的兩個安全問題: 跨站請求偽造 和跨站腳本攻擊。 好激動,終于到安全部分的知識了!??!
互聯(lián)網(wǎng)瀏覽器和網(wǎng)站之間的關(guān)系就像我和餐廳的關(guān)系一樣。這一關(guān)系包含 了大量的請求和響應(yīng)。我的請求是一份晚餐,廚房的響應(yīng)則是將我點的菜 做出來并送到我的面前。
跨站請求偽造,即 CSRF(cross-site request forgery,讀作 sea-surf),是一種 利用站點對用戶瀏覽器信任而發(fā)起攻擊的方式。CSRF 漏洞已經(jīng)存在了很長 時間,遠(yuǎn)比 JSON 出現(xiàn)得早。
接下來看一個利用 JSON 進(jìn)行 CSRF 攻擊的例子。
你登錄了一個銀行的網(wǎng)站,這個網(wǎng)站有一個關(guān)于你的敏感信息的 JSON URL
JSON 中保存的你的敏感信息
[ { "user": "bobbarker" }, { "phone": "555-555-5555" } ]
你可能會說:“嘿,你的 JSON 最外面沒加花括號!”其實這里的 JSON 格 式是合法的。不過有些合法的 JSON 十分危險,因為它們也是可以執(zhí)行的 JavaScript 腳本。本例這種情況也被稱為頂層 JSON 數(shù)組。
本例中的站點使用會話 cookie 驗證,以確保信息是傳送給像你一樣已經(jīng)注 冊且處于登錄狀態(tài)的用戶。
而示例中的黑客在發(fā)現(xiàn)銀行網(wǎng)站上保存敏感信息的 JSON 的 URL 后,會把 它放到指向自己站點的 <script>
標(biāo)簽中。
瀏 覽 器 對 于 不 同 域 名的站點之間進(jìn)行資源分享有一定的限制規(guī)則。但是<script>
是可以加載外部資源的,利用這個特性:
黑客站點的
<script>
標(biāo)簽可能是這個樣子的
<script src="https://www.yourspecialbank.com/user.json"></script>
這樣的攻擊能夠?qū)崿F(xiàn),很關(guān)鍵的一點就是利用了你和網(wǎng)站間的憑證。沒有你的憑證,<script>
標(biāo)簽中的鏈接是不會返回任何東西的。
CSRF 攻擊就利用了這一信任關(guān)系。黑客為了利用這一信任,需要你在已經(jīng) 登錄銀行的情況下訪問他含有危險的 <script>
標(biāo)簽的網(wǎng)站。要實現(xiàn)這點, 他會發(fā)送大量的偽造郵件,內(nèi)容大致是“銀行提醒您:重要消息”。這類郵 件會偽造得和你的銀行(或它們想攻擊的網(wǎng)站)發(fā)來的郵件一模一樣。如 果接收者不好好查看一下發(fā)件人,或不仔細(xì)查看一下鏈接是否是指向他們 可以信任的網(wǎng)站,就很可能會點進(jìn)去。
假如你不小心點擊了這個鏈 接。如果這時你沒有退出銀行賬號,會話就仍然存在。此時你和銀行之間 處于信任的關(guān)系中。一旦你進(jìn)入了壞人的網(wǎng)站,意識到這個站點很奇怪, 就關(guān)閉了。不過這時已經(jīng)晚了。黑客已然獲取到了敏感的 JSON 數(shù)據(jù)并發(fā) 送到了自己服務(wù)器上保存起來。
那么銀行及其開發(fā)者應(yīng)如何阻止 CSRF 攻擊呢?
首先,銀行應(yīng)該將數(shù)組作為一個值存入 JSON 對象。這樣數(shù)組將不再是合法的 JavaScript
將數(shù)組存放到對象之中,使其成為非法的 JavaScript,這樣就不 會被
<script>
標(biāo)簽加載
{ "info": [ { "user": "bobbarker" }, { "phone": "555-555-5555" } ] }
下一步,銀行應(yīng)僅允許 POST 請求獲取數(shù)據(jù),禁止使用 GET 請求,這樣黑客 便無法使用他自己的 URL 中的鏈接了。如果服務(wù)器端允許 GET 請求,就可以直接通過瀏覽器 或 <script>
標(biāo)簽鏈接到它。但 POST 則不可以直接被鏈接到。一旦不能使用 <script>
標(biāo)簽,黑客就會受到資源共享策略的限制,從而無法通過利用銀 行對客戶端的信任進(jìn)行欺騙行為。
當(dāng)然,這并不意味著通過 HTTP 進(jìn)行的 JSON 數(shù)據(jù)交換都應(yīng)通過 POST 來進(jìn) 行。當(dāng)JSON存在敏感數(shù)據(jù)的時候應(yīng)該考慮使用POST來提交數(shù)據(jù),其他不重要的數(shù)據(jù) 怎么開心怎么寫。
注入攻擊包含許多種形式與格式。不過,它們都是利用系統(tǒng)本身的漏洞來 實現(xiàn)的。CSRF 攻擊僅包括利用信任機制進(jìn)行的攻擊。注入攻擊則主要通過 向網(wǎng)站注入惡意代碼來實現(xiàn)。
跨站腳本攻擊(cross-site scripting,XSS)是注入攻擊的一種。
JSON 本身僅僅是文本,但是當(dāng)用戶在JavaScript匯總使用 eval()
函數(shù)來解析JSON的時候可能出現(xiàn)安全問題,導(dǎo)致XSS漏洞的產(chǎn)生。
隨著 JSON 本身的不斷發(fā)展,這一漏洞已得到公認(rèn)。JSON.parse()
函數(shù)就 是用來處理這一問題的。該函數(shù)僅會解析 JSON,并不會執(zhí)行腳本。
使用 JSON.parse() 代替 eval()
var jsonString = '{"animal":"cat"}'; var myObject = JSON.parse(jsonString); alert(myObject.animal);
有時候危險也會直接包含在合法的 JSON 數(shù)據(jù)之中。
讓我們先來看一些很規(guī)矩的 JSON 數(shù)據(jù)
{ "message": "hello, world!" }
假設(shè)我的站點允許一個用戶向另一個用戶發(fā)送可以包含任何內(nèi)容的消息。
在消息界面, 我從服務(wù)端請求 JSON 格式的消息并在客戶端使用 eval()
函數(shù)來將 JSON轉(zhuǎn)換成可以使用的 JavaScript 對象。然后我將該 JavaScript 對象的 message 屬性的值直接顯示在 HTML 中。
不那么規(guī)矩的 JSON
{ "message": "<div onmouseover=\"alert('gotcha!')\">hover here.</div>"}
這樣就直接導(dǎo)致了XSS漏洞的產(chǎn)生,問題出在最后JSON數(shù)據(jù)輸出的時候 導(dǎo)致了XSS漏洞的產(chǎn)生,雖然傳輸?shù)臅r候很安全,但是最后顯示在了HTML頁面上。和我以前遇到的這個漏洞原理一樣:記錄一次某客戶系統(tǒng)的漏洞挖掘 XSS1
如何阻止這種情況呢?一方面,可以采取一些手段使得消息中不包含 HTML,將HTML特殊字符實體化編碼來修復(fù)這個問題。
JavaScript 中的 XMLHttpRequest 與 Web API 等概念聽上去好像很難,但實 際上并沒有想象中那么復(fù)雜。它僅僅是一種簡單的客戶端與服務(wù)端的關(guān)系。
JavaScript 中的 XMLHttpRequest 負(fù)責(zé)在客戶端發(fā)起請求,而 Web API 負(fù)責(zé)在 服務(wù)端返回響應(yīng)。
JSON 資源可以通過一個 URL 來請求,Web API就是這個URL,負(fù)責(zé)返回給客戶JSON格式的信息,然后客戶端再解析這串JSON數(shù)據(jù)來達(dá)到數(shù)據(jù)顯示的效果。
盡管 JavaScript 的 XMLHttpRequest
對象聽上去和 XML 有關(guān),但實際上我們 使用它來發(fā)起 HTTP 請求。
JavaScript 中使用協(xié)議來發(fā)送這類請求的代碼就是 XMLHttpRequest
。 JavaScript 是一種面向?qū)ο蟮恼Z言,而 XMLHttpRequest
就是一類對象。
JavaScript 中的 XMLHttpRequest 對象
var myXmlHttpRequest = new XMLHttpRequest();
這個對象的熟悉比較復(fù)雜和混亂,國光這里就不深入了。
我在這里留給了你一些懸念。我展示了一些能夠運行的代碼,但是它們違 背了瀏覽器的同源策略。它們本來不應(yīng)該跑通,但是卻跑通了,這是因為開發(fā)者們繞過了一些限制。
有些開發(fā)人員可以連續(xù)多年通過 JavaScript 的 AJAX 技術(shù)向公共 API 發(fā)送請 求,而不會受到同源策略的影響。這是因為這些公共 API 的開發(fā)者在他們 的服務(wù)器上實現(xiàn)了跨域資源共享(cross-origin resource sharing,CORS)。
這 些服務(wù)器會在響應(yīng)頭額外加上一些帶有 Access-Control-Allow 前綴的屬性:
API 返 回 的 響 應(yīng) 頭 中 的 Access- Control-Allow 部分
Access-Control-Allow-Credentials: trueAccess-Control-Allow-Methods: GET, POST Access-Control-Allow-Origin: *
這些頭部定義了
證書是否可用,
哪些 HTTP 方法(GET、POST、PUT、DELETE、 HEAD、OPTIONS、TRACE、CONNECT)是可用的,
以及最重要的一點,即允許哪些域名,本例中,此處包含一個星號*
。它表示任意域名都是允許的。
CORS 還可用于禁止某些域名的 訪問,來防止有些人用 API 做壞事,如 CSRF:
使用 CORS 進(jìn)行安全防護(hù)
Access-Control-Allow-Methods: POSTAccess-Control-Allow-Origin: http://www.somebank.com
我們僅允許通過 POST 方式請求資源,禁止除 http://www.somebank.co
以外的站點去獲取資源
使用 CORS 也會遇到一些問題,而且也不是所有的 JSON 數(shù)據(jù)都是從標(biāo)準(zhǔn) 的 Web API 獲取的。
例如可能你有兩個域名不同的站點(http://a.com 和 http://b.com), 且 想 讓 http://a.com 與 http://b.com 共享一些 JSON 文件,這就需要使用 JSON-P 了。
JSON-P 指帶有 padding
(內(nèi)聯(lián)) 的 JSON。我在第 5 章提到過 <script>
標(biāo)簽不受同源策略的影響。JSON-P 就是利用這一點來向不同域名的站點請求 JSON 的。
JSON-P 并沒有 CORS 那么理想,它只是一個備選方案(CORS 是更好的 方案,同時也是一種標(biāo)準(zhǔn)),但有時候還是需要這種解決方案的。
JSON-P 中的padding
(內(nèi)聯(lián))非常簡單,就是將 JavaScript 加入 JSON 文 檔。
JSON-P
getTheAnimal( { "animal": "cat" });
內(nèi)聯(lián)于 JSON 文檔的 JavaScript 調(diào)用了一個函數(shù),函數(shù)參數(shù)是 JSON。函數(shù) 參數(shù)提供了一種將數(shù)據(jù)傳遞給函數(shù)的方式。該函數(shù)是在客戶端的 JavaScript 代碼中定義的
在 JavaScript 中聲明的函數(shù)
function getTheAnimal(data) { var myAnimal = data.animal; // will be "cat"}
當(dāng)在 JavaScript 中聲明該函數(shù)之后,需要進(jìn)行一些設(shè)置。這部分就體現(xiàn)出 了 JSON-P 是如何利用 <script>
標(biāo)簽不受同源策略影響這一點的
創(chuàng)建
<script>
標(biāo)簽,并將其動態(tài)添加到 HTML 文檔的<head>
標(biāo)簽中
var script = document.createElement("script");script.type = "text/javascript";script.src = "http://notarealdomain.com/animal.json"; document.getElementsByTagName('head')[0].appendChild(script);
服務(wù)端也需要對 JSON-P 提供一定的支持,它應(yīng)允許用戶自定義函數(shù)的名 字。它通常是作為 URL 中 callback 的參數(shù)傳遞的。
通過 callback 告知服務(wù)器函數(shù)的名字
script.src = "http://notarealdomain.com/animal.json?callback=getThing";
服務(wù)端會根據(jù) callback 參數(shù)的值來動態(tài)地為在 JSON 中內(nèi)聯(lián)的函數(shù)命名。
JSON-P 中動態(tài)命名的函數(shù)
getThing( { "animal": "cat" });
JSON-P 還需要服務(wù)端的不少支持,因為 JSON 資源必須包含 JavaScript 內(nèi) 聯(lián)。不管是使用 CORS 還是 JSON-P,都離不開服務(wù)端的支持。因此,客戶端跨域的 XMLHttpRequest 需要服務(wù)端的支持來保證 JSON 資源請求成功。
書本這章看完了我對JSONP的理解還是有很多不明白的地方,本小節(jié)是國光自己知乎找的理解。
賀師俊 JavaScrip、前端開發(fā)、CSS等 4 個話題的優(yōu)秀回答者
很簡單,就是利用<script>
標(biāo)簽沒有跨域限制的“漏洞”(歷史遺跡?。﹣磉_(dá)到與第三方通訊的目的。
當(dāng)需要通訊時,本站腳本創(chuàng)建一個<script>
元素,地址指向第三方的API網(wǎng)址,形如:
<script src="http://www.example.net/api?param1=1¶m2=2"></script>
并提供一個回調(diào)函數(shù)來接收數(shù)據(jù)(函數(shù)名可約定,或通過地址參數(shù)傳遞)。
第三方產(chǎn)生的響應(yīng)為JSON數(shù)據(jù)的包裝(故稱之為JSON,即JSON padding),形如
callback({"name":"hax","gender":"Male"})
這樣瀏覽器會調(diào)用callback
函數(shù),并傳遞解析后JSON對象作為參數(shù)。
本站腳本可在callback函數(shù)里處理所傳入的數(shù)據(jù)。
Ddd Poplar
JSONP是一種非正式傳輸協(xié)議,該協(xié)議的一個要點就是允許用戶傳遞一個callback參數(shù)給服務(wù)端,然后服務(wù)端返回數(shù)據(jù)時會將這個callback參數(shù)作為函數(shù)名來包裹住JSON數(shù)據(jù),這樣客戶端就可以隨意定制自己的函數(shù)來自動處理返回數(shù)據(jù)了。
黃之昊 手藝人/前端
JSON 是一種數(shù)據(jù)格式
JSONP 是一種數(shù)據(jù)調(diào)用的方式。
你可以簡單的理解為 帶callback的JSON就是JSONP
柴郡貓Kate 程序員
JSONP利用<script>
標(biāo)簽引用資源不受同源策略的影響的特性,向服務(wù)器端發(fā)送請求時在url中拼接一個callback函數(shù),然后將服務(wù)器端的返回作為callback函數(shù)的參數(shù)進(jìn)行調(diào)用,從而達(dá)到跨域資源請求的目的。
示例:
var callbackHandler = function(data) { process(data) //對返回數(shù)據(jù)進(jìn)行處理}<script src="http://xxx.com/xxx?callback=callbackHandler" />
知乎看完了,貌似對JSONP懂一點了,但還是有疑惑,于是看了網(wǎng)友們推薦的這篇文章:
【原創(chuàng)】說說JSON和JSONP,也許你會豁然開朗,含jQuery用例
JSON是一種數(shù)據(jù)交換格式,而JSONP是一種非官方跨域數(shù)據(jù)交互協(xié)議。一個是描述信息的格式,一個是信息傳遞雙方約定的方法。
Ajax直接請求普通文件存在跨域無權(quán)限訪問的問題
Web頁面上調(diào)用js文件時則不受是否跨域的影響 我們還發(fā)現(xiàn)凡是擁有”src”這個屬性的標(biāo)簽都擁有跨域的能力,比如<script>
、<img>
、<iframe>
純web端跨域訪問數(shù)據(jù)就只有一種可能,那就是在遠(yuǎn)程服務(wù)器上設(shè)法把數(shù)據(jù)裝進(jìn)js格式的文件里,供客戶端調(diào)用和進(jìn)一步處理
JSON的純字符數(shù)據(jù)格式可以簡潔的描述復(fù)雜數(shù)據(jù),而且被原生JS支持
Web客戶端通過與調(diào)用腳本一模一樣的方式,來調(diào)用跨域服務(wù)器上動態(tài)生成的js格式文件(一般以JSON為后綴),顯而易見,服務(wù)器之所以要動態(tài)生成JSON文件,目的就在于把客戶端需要的數(shù)據(jù)裝入進(jìn)去。
客戶端在對JSON文件調(diào)用成功之后,也就獲得了自己所需的數(shù)據(jù),剩下的就是按照自己需求進(jìn)行處理和展現(xiàn)了,這種獲取遠(yuǎn)程數(shù)據(jù)的方式看起來非常像AJAX,但其實并不一樣。
為了便于客戶端使用數(shù)據(jù),逐漸形成了一種非正式傳輸協(xié)議,人們把它稱作JSONP,該協(xié)議的一個要點就是允許用戶傳遞一個callback
參數(shù)給服務(wù)端,然后服務(wù)端返回數(shù)據(jù)時會將這個callback參數(shù)作為函數(shù)名來包裹住JSON數(shù)據(jù),這樣客戶端就可以隨意定制自己的函數(shù)來自動處理返回數(shù)據(jù)
遠(yuǎn)程服務(wù)器remoteserver.com
根目錄下有個remote.js
文件代碼如下:
alert('我是遠(yuǎn)程文件');
本地服務(wù)器localserver.com
下有個jsonp.html
頁面代碼如下:
<script src="http://remoteserver.com/remote.js"></script>
毫無疑問,頁面將會彈出一個提示窗體,表明跨域調(diào)用成功。
現(xiàn)在我們在jsonp.html
頁面定義一個函數(shù),然后在遠(yuǎn)程remote.js
中傳入數(shù)據(jù)進(jìn)行調(diào)用。
jsonp.html頁面代碼如下:
<script>var localHandler = function(data){ alert('我是本地函數(shù),可以被跨域的remote.js文件調(diào)用,遠(yuǎn)程js帶來的數(shù)據(jù)是:' + data.result);};</script><script src="http://remoteserver.com/remote.js"></script>
remote.js
文件代碼如下:
localHandler({"result":"我是遠(yuǎn)程js帶來的數(shù)據(jù)"});
運行之后查看結(jié)果,頁面成功彈出提示窗口,哇塞 好神奇!顯示本地函數(shù)被跨域的遠(yuǎn)程js調(diào)用成功,并且還接收到了遠(yuǎn)程js帶來的數(shù)據(jù)。
很欣喜,跨域遠(yuǎn)程獲取數(shù)據(jù)的目的基本實現(xiàn)了,但是又一個問題出現(xiàn)了,我怎么讓遠(yuǎn)程js知道它應(yīng)該調(diào)用的本地函數(shù)叫什么名字呢?畢竟是JSONP的服務(wù)者都要面對很多服務(wù)對象,而這些服務(wù)對象各自的本地函數(shù)都不相同???我們接著往下看。
聰明的開發(fā)者很容易想到,只要服務(wù)端提供的js腳本是動態(tài)生成的就行了唄,這樣調(diào)用者可以傳一個參數(shù)過去告訴服務(wù)端“我想要一段調(diào)用XXX函數(shù)的js代碼,請你返回給我”,于是服務(wù)器就可以按照客戶端的需求來生成js腳本并響應(yīng)了。
看jsonp.html頁面的代碼:
<script>// 得到航班信息查詢結(jié)果后的回調(diào)函數(shù)var flightHandler = function(data){ alert('你查詢的航班結(jié)果是:票價 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 張。');};// 提供jsonp服務(wù)的url地址(不管是什么類型的地址,最終生成的返回值都是一段javascript代碼)var url = "http://a.com/api.php?code=CA1998&callback=flightHandler";// 創(chuàng)建script標(biāo)簽,設(shè)置其屬性var script = document.createElement('script');script.setAttribute('src', url);// 把script標(biāo)簽加入head,此時調(diào)用開始document.getElementsByTagName('head')[0].appendChild(script); </script>
這次的代碼變化比較大,不再直接把遠(yuǎn)程js文件寫死,而是編碼實現(xiàn)動態(tài)查詢,而這也正是jsonp客戶端實現(xiàn)的核心部分,本例中的重點也就在于如何完成jsonp調(diào)用的全過程。
我們看到調(diào)用的url中傳遞了一個code
參數(shù),告訴服務(wù)器我要查的是CA1998
次航班的信息,而callback
參數(shù)則告訴服務(wù)器,我的本地回調(diào)函數(shù)叫做flightHandler
,所以請把查詢結(jié)果傳入這個函數(shù)中進(jìn)行調(diào)用。
OK,服務(wù)器很聰明,這個叫做api.php
的頁面生成了一段這樣的代碼提供給jsonp.html(服務(wù)端的實現(xiàn)這里就不演示了,與你選用的語言無關(guān),說到底就是拼接字符串):
flightHandler({ "code": "CA1998", "price": 1780, "tickets": 5});
我們看到,傳遞給flightHandler
函數(shù)的是一個json,它描述了航班的基本信息。運行一下頁面,成功彈出提示窗口,JSONP的執(zhí)行全過程順利完成!
到這里為止的話,相信你已經(jīng)能夠理解jsonp的客戶端實現(xiàn)原理了吧?剩下的就是如何把代碼封裝一下,以便于與用戶界面交互,從而實現(xiàn)多次和重復(fù)調(diào)用。
什么?你用的是jQuery,想知道jQuery如何實現(xiàn)jsonp調(diào)用?好吧,那我就好人做到底,再給你一段jQuery使用JSONP的代碼(我們依然沿用上面那個航班信息查詢的例子,假定返回JSONP結(jié)果不變):
<script src="jquery.min.js"></script><script> jQuery(document).ready(function(){ $.ajax({ type: "get", async: false, url: "http://a.com/api.php?code=CA1998", dataType: "jsonp", jsonp: "callback", //傳遞給請求處理程序或頁面的,用以獲得jsonp回調(diào)函數(shù)名的參數(shù)名(一般默認(rèn)為:callback) jsonpCallback:"flightHandler", //自定義的jsonp回調(diào)函數(shù)名稱,默認(rèn)為jQuery自動生成的隨機函數(shù)名,也可以寫"?",jQuery會自動為你處理數(shù)據(jù) success: function(json){ alert('您查詢到航班信息:票價: ' + json.price + ' 元,余票: ' + json.tickets + ' 張。'); }, error: function(){ alert('fail'); } }); });</script>
是不是有點奇怪?為什么我這次沒有寫flightHandler這個函數(shù)呢?而且竟然也運行成功了!哈哈,這就是jQuery的功勞了,jQuery在處理JSONP類型的ajax時(還是忍不住吐槽,雖然jquery也把JSONP歸入了ajax,但其實它們真的不是一回事兒),自動幫你生成回調(diào)函數(shù)并把數(shù)據(jù)取出來供success屬性方法來調(diào)用,是不是很爽呀?
!!! 國光我看懂了,頓時豁然開朗,以后寫文章也要向這位博主學(xué)習(xí),把知識講的生動形象,淺顯易懂。
如今,有許多(>50)種 JavaScript 框架可供選擇,讓我們來了解一些 JavaScript 框架,以及它們與 JSON 的關(guān)系。
jQuery可以讓開發(fā)者操作DOM更簡單,比如將一個 HTML 元素隱藏。
先創(chuàng)建一個按鈕,然后隱藏它
一個在瀏覽器中顯示為“My Button”的按鈕
<button id="myButton">My Button</button>
為使用 JavaScript 代碼隱藏這個按鈕,首先要調(diào)用 HTML 的 document 對象的 getElementById(id)
方法,然后將對應(yīng)的 style.display
屬性值設(shè)為 "none"
。
用于隱藏“My Button”的 JavaScript 代碼
document.getElementById("myButton").style.display = "none";
若使用 jQuery,可以用不到上例一半長度的代碼實現(xiàn)同樣的功能
用于隱藏“My Button”的 jQuery 代碼
$("#myButton").hide();
jQuery的兼容性也很好,在第 5 章中我用 更為安全的 JSON.parse()
方法代替 eval()
方法來解析 JSON,但是老版本的瀏覽器不支持這種安全的方法,這里jQuery 有自己的解析 JSON 的方法:用 jQuery.parseJSON
函數(shù)。
使用 JavaScript 中的 JSON.parse() 解析 JSON
var myAnimal = JSON.parse('{ "animal" : "cat" }');
使用 jQuery 內(nèi)置的 jQuery.parseJSON 來解析 JSON
var myAnimal = jQuery.parseJSON('{ "animal" : "cat" }');
上面的例子中,使用 jQuery 并沒有在代碼長度上面體現(xiàn)出優(yōu)勢。但是這里是兼容老版本瀏覽器的,如果深入研究下去的話 會發(fā)現(xiàn)它會先嘗試使用原生的 JSON.parse()
函數(shù)。 如果瀏覽器不支持,它會使用和eval()
功能類似的new Function()
。同時, 它會對一些不合法的字符進(jìn)行檢測,如果發(fā)現(xiàn)其中有注入攻擊的威脅,就 會拋出錯誤。
除了jQuery.parseJSON
以外,還有一個用于通過 HTTP 請求 JSON 的函數(shù)。
回顧第 6 章,在使用 JavaScript 進(jìn)行 HTTP 請求時,寫了不少代碼。
創(chuàng)建一個新的 XMLHttpRequest 對象并從 OpenWeatherMap API 獲 取 JSON
var myXMLHttpRequest = new XMLHttpRequest();var url = "http://api.openweathermap.org/data/2.5/weather?lat=35&lon=139";myXMLHttpRequest.onreadystatechange = function() { if (myXMLHttpRequest.readyState === 4 && myXMLHttpRequest.status === 200) { var myObject = JSON.parse(myXMLHttpRequest.responseText); var myJSON = JSON.stringify(myObject); } }myXMLHttpRequest.open("GET", url, true); myXMLHttpRequest.send();
使用 jQuery 僅需幾行代碼即可完成對 JSON 數(shù)據(jù)的請求
這段 jQuery 代碼創(chuàng)建一個新的 XMLHttpRequest 對 象并獲取 JSON 數(shù)據(jù),還將 JSON 數(shù)據(jù)反序列化為一個 JavaScript 對象
var url = "http://api.openweathermap.org/data/2.5/ weather?lat=35&lon=139";$.getJSON(url, function(data) {// 對天氣數(shù)據(jù)執(zhí)行一些操作 });});
jQuery 真的很方便吶!
jQuery 框架是為 DOM 操作服務(wù)的抽象化工具,AngularJS 框架是專注于創(chuàng)建單頁應(yīng)用的抽象化工具。
這里國光認(rèn)為還是jQuery用的比較多,所以這里我就不再多花時間了,等以后轉(zhuǎn)行做開發(fā)了再考慮看看。
NoSQL,顧名思義,它不是一種關(guān)系型數(shù)據(jù)庫。我們不能使用 SQL 從 關(guān)聯(lián)在一起的數(shù)據(jù)庫表格的行和列中獲取數(shù)據(jù)。
NoSQL 數(shù)據(jù)庫的一個例子是鍵值對存儲。鍵值對存儲模型將數(shù)據(jù)簡化為鍵 值對。如果要將英語詞典編入數(shù)據(jù)庫,那么用鍵值對存儲非常合適。每一 個單詞就是一個鍵,單詞對應(yīng)的定義就是鍵的值。對于比較簡單的數(shù)據(jù)結(jié) 構(gòu)來說,使用這種數(shù)據(jù)庫比使用傳統(tǒng)的關(guān)系型數(shù)據(jù)庫要合適。
本章我們來看一種使用 JSON 文檔存儲數(shù)據(jù)的文檔存儲數(shù)據(jù)庫——CouchDB。
CouchDB 是一種使用 JSON 文檔存儲數(shù)據(jù)的NoSQL 數(shù)據(jù)庫。
使用數(shù)組中的對象來表示賬戶和地址間的一對多關(guān)系。
使用 JSON 文檔來表示賬戶
{ "firstName": "Bob", "lastName": "Barker", "age": 91, "addresses": [ { "street": "123 fake st", "city": "Somewhere", "state": "OR", "zip": 97520 }, { "street": "456 fakey ln", "city": "Some Place", "state": "CA", "zip": 96026 } ]}
由于 CouchDB 使用文檔來存儲數(shù)據(jù),因此當(dāng)我從數(shù)據(jù)庫中查詢一個賬戶時, 得到的直接就是一個結(jié)構(gòu)化的文檔。沒有必要進(jìn)行重組。這樣既高效又方便。
但是在更復(fù)雜的數(shù)據(jù)情況下,還是老老實實使用關(guān)系型數(shù)據(jù)庫吧,用這種文檔來表示就很蛋疼了。
CouchDB可以很方便地添加字段,無需結(jié)構(gòu)改動。
那么問題來了,該怎么把 數(shù)據(jù)放進(jìn)去,又怎么把數(shù)據(jù)拿出來? CouchDB 使用基于 HTTP 的 API 實現(xiàn) 這一功能?,F(xiàn)在來看看 CouchDB API。
CouchDB API的默認(rèn)端口是5984,查了下 果然這個端口歷史上存在未授權(quán)訪問漏洞,并且還可以造成命令執(zhí)行…
以后這個端口的話 就知道了,還有這種操作。
這個章節(jié)說了如何使用用這個CouchDB API,國光這里暫時不深入,以后有空了再詳細(xì)閱讀。
當(dāng)談及 Web 客戶端 - 服務(wù)端關(guān)系時,可以將技術(shù)按客戶端和服務(wù)端的區(qū)別 分類。
· 客戶端:HTML、CSS、JavaScript。
· 服務(wù)端:PHP、ASP.NET、Node.js、Ruby on Rails、Java、Go,等等
書里面居然沒有Python… 尷尬
在 Web 客戶端,我們通過 HTTP 向服務(wù)端發(fā)送資源請求。服務(wù)端會響應(yīng)一 份文檔,如 HTML 或 JSON。當(dāng)文檔是 JSON 時,必須要用服務(wù)端代碼來 生成它。
不過,JavaScript 并不是唯一一個可以通過 HTTP 請求獲取 JSON 資源的語言。服務(wù)端的 Web 框架也可以發(fā)起 HTTP 請求。
當(dāng)通過服務(wù)端技術(shù)來請求 JSON 時,服務(wù)端扮演的其實是客戶端的角色。 下面列舉了一些需要用服務(wù)端 Web 框架發(fā)送 HTTP 請求的例子。
大部分服務(wù)端 Web 框架以及腳本語言都較好地支持 JSON
.NET 這個古老的語言要涼,國光這里就不浪費時間了 直接跳過
表示一只貓的一個 PHP 類
class Cat { public $name; public $breed; public $age; public $declawed;}
類的實例化意味著對象的創(chuàng)建。之后該對象便可在編程邏輯中使用
將類實例化并設(shè)置屬性。這意味著對象被創(chuàng)建了。最后一行會 輸出 “Fluffy Boo”
$cat = new Cat(); $cat->name = "Fluffy Boo"; $cat->breed = "Maine Coon"; $cat->age = 2.5; $cat->declawed = false;echo $cat->name;
PHP 對 JSON 的序列化和反序列化操作也有內(nèi)置的支持。這類操作在 PHP 中稱為 JSON 的編碼與解碼。
因此,序列化 JSON 時應(yīng)調(diào)用 json_encode
函數(shù),反序列化 JSON 時應(yīng)調(diào)用 json_decode
函數(shù)。
在 PHP 中,可以通過內(nèi)置的 JSON 支持快速地將 PHP 對象序列化。這一部分中,我們將創(chuàng)建一個保存地址信息的 PHP 對象,并將其序列化為 JSON。
創(chuàng)建并序列化一個賬戶
<?phpclass Account { public $firstName; public $lastName; public $phone; public $gender; public $addresses; public $famous;}class Address { public $street; public $city; public $state; public $zip;}$address1 = new Address();$address1->street = "123 fakey st";$address1->city = "Somewhere";$address1->state = "CA";$address1->zip = 96027;$address2 = new Address();$address2->street = "456 fake dr";$address2->city = "Some Place";$address2->state = "CA";$address2->zip = 96345;$account = new Account();$account->firstName = "Bob";$account->lastName = "Barker";$account->gender = "male";$account->phone = "555-555-5555";$account->famous = true;$account->addresses = array($address1, $address2);$json = json_encode($account);?>
json_encode($account) 返回的結(jié)果
{ "firstName": "Bob", "lastName": "Barker", "phone": "555-555-5555", "gender": "male", "addresses": [{ "street": "123 fakey st", "city": "Somewhere", "state": "CA", "zip": 96027 }, { "street": "456 fake dr", "city": "Some Place", "state": "CA", "zip": 96345 } ], "famous": true}
使用 json_ decode
函數(shù)來反序列化 JSON。不過,內(nèi)置的方法并不支持將 JSON 反序列 化為某種具體的 PHP 對象類型,如 Account
類。所以,需要采取一些措施 將數(shù)據(jù)重塑為對應(yīng)的 PHP 對象。
首先給 Account
對象添加一個新函數(shù),使它可以通過 JSON 字符串設(shè)置自己 的屬性。
示例中,loadFromJSON
函數(shù)接受一個 JSON 字符串作為參數(shù),它會調(diào) 用內(nèi)置的json_decode
函數(shù)反序列化一個一般的 PHP 對象,然后在 foreach
循環(huán)中遍歷該對象的屬性,并賦值給 Account
對象中名稱相同的屬性。
向 Account 對象添加一個函數(shù)
class Account { public $firstName; public $lastName; public $phone; public $gender; public $addresses; public $famous; public function loadFromJSON($json) { $object = json_decode($json); foreach ($object AS $name => $value) { $this-> { $name } = $value; } }}
接下來創(chuàng)建一個新的 Account 對象,并調(diào)用新的 loadFromJSON 函數(shù)
調(diào)用 loadFromJSON 函數(shù)來將賬戶的 JSON 反序列化為 Account 對象;最后一行會輸出 “Bob Barker”
$json = json_encode($account);$deserializedAccount = new Account();$deserializedAccount->loadFromJSON($json);echo $deserializedAccount->firstName . " " . $deserializedAccount-> lastName;
使用 PHP 內(nèi)置的 file_get_contents
函數(shù)來創(chuàng)建 HTTP 請求。該函數(shù)會將資源以字符串的形式返回。然后就可以將字符串反序列化為 PHP 對象
本地 CouchDB API 在 http://localhost:5984/accounts/ddc14efcf- 71396463f53c0f8800019ea 響應(yīng)的資源
{ "_id": "ddc14efcf71396463f53c0f8800019ea", "_rev": "6-69fd853972074668f99b88a86aa6a083", "address": { "street": "123 fakey ln", "city": "Some Place", "state": "CA", "zip": "96037" }, "gender": "female", "famous": false, "age": 28, "firstName": "Mary", "lastName": "Thomas"}
調(diào)用內(nèi)置的 file_get_contents 函數(shù)請求上例中的 JSON 資源。 然后創(chuàng)建一個新的 Account 對象,并調(diào)用 loadFromJSON 進(jìn)行反 序列化操作。最后一行將輸出 “Mary Thomas”
$url = "http://localhost:5984/accounts/3636fa3c716f9dd4f7407bd6f700076c";$json = file_get_contents($url);$deserializedAccount = new Account();$deserializedAccount->loadFromJSON($json);echo $deserializedAccount->firstName . " " . $deserializedAccount-> lastName;
接下來看看服務(wù)端上一些請求 JSON 的小例子。在每個小例子中,都能看 到使用 HTTP 請求 JSON 資源,將資源解析為對象,以及 JSON 文檔中的值 的渲染。
下面所有示例使用的 JSON 文檔都來源于 OpenWeatherMap API。渲染的值都基于 http://api.openweathermap.org/data/2.5/weather?q=London,uk這 一 JSON 文檔的子集
OpenWeatherMap API 中 JSON 資源的坐標(biāo)對象,其值代表倫 敦的經(jīng)緯度
{ "coord": { "lon": -0.13, "lat": 51.51 }}
Ruby on Rails 是一個服務(wù)端的 Web 應(yīng)用框架。它使用 Ruby 語言編寫且基 于模型 - 視圖 - 控制器(MVC)架構(gòu)。
我們需要 JSON ruby gem。一旦裝好了 JSON ruby gem,解析 JSON 就變得很簡單了,只需執(zhí)行 JSON.parse()
向 OpenWeatherMap API 發(fā)送請求,并將 JSON 反序列化
為 Ruby 對象。借助代碼的最后一行,我們在頁面中渲染出了數(shù)據(jù)中坐標(biāo) (coord)對象中的經(jīng)度值(lon)。
向 OpenWeatherMap API 發(fā)送請求
require 'net/http'require 'json'url = URI.parse('http://api.openweathermap.org/data/2.5/ weather?q=London,uk')request = Net::HTTP::Get.new(url.to_s)response = Net::HTTP.start(url.host, url.port) {|http| http.request(request)}weatherData = JSON.parse(response.body) render text: weatherData["coord"]["lon"]
Node.js 是服務(wù)端的 JavaScript(脫離了瀏覽器),它基于谷歌的開源JavaScript 引擎 V8。有了 Node.js,就可以使用 JavaScript 編寫服務(wù)端應(yīng)用。
前面的章節(jié)探討過 JSON 與客戶端的 JavaScript,并通過 JSON.parse()
輕松 地將 JSON 反序列化為 JavaScript 對象。因為 Node.js 也是 JavaScript,所以 方法都是一樣的。
不過,在 Node.js 中不再使用 XMLHttpRequest
對象,因為該對象是僅在互聯(lián) 網(wǎng)瀏覽器中使用的 JavaScript 對象。在 Node.js 中,通過更簡單的 get()
函 數(shù)來請求 JSON(以及其他類型的資源)。
示例中,向 OpenWeatherMap API 發(fā)送請求,并將得到的 JSON 反序 列化為 JavaScript 對象。然后通過 console.log()
輸出坐標(biāo)(coord)對象中的經(jīng)度值(lon)。
向 API 發(fā)送 HTTP 請求并將其反序列化為 JavaScript 對象
var http = require('http'); http.get({ host: 'api.openweathermap.org', path: '/data/2.5/weather?q=London,uk'}, function (response) { var body = ''; response.on('data', function (data) { body += data; }); response.on('end', function () { var weatherData = JSON.parse(body); console.log(weatherData.coord.lon); });});
Java 是一種面向?qū)ο蟮木幊陶Z言。Java 中的許多庫都提供了對 JSON 的支持。在這一部分中,我將使用下面 的庫來通過 URL 獲取 JSON,并將其序列化為 Java 對象。
示例中,從 OpenWeatherMap API 中以字符串的形式獲取 JSON 資源。 然后通過實例化 JSONObject
將 JSON 字符串反序列化。最后使用 System. out.println()
輸出坐標(biāo)(coord)對象中的經(jīng)度值(lon)。
通過 JSONObject 反序列化 JSON 字符串
import org.apache.commons.io.IOUtils;import org.json.JSONException;import org.json.JSONObject;import java.io.IOException;import java.net.URL;public class HelloWorldApp { public static void main(String[] args) throws IOException, JSONException { String url = "http://api.openweathermap.org/data/2.5/ weather?q=London,uk"; String json = IOUtils.toString(new URL(url)); JSONObject weatherData = new JSONObject(json); JSONObject coordinates = weatherData.getJSONObject("coord"); System.out.println(coordinates.get("lon")); }}
JSON用途并沒有局限于某一個方 面。我們知道它在NoSQL
中是存儲數(shù)據(jù)的載體。而且,JSON 也在逐漸成 為項目中有用的工具。
為了進(jìn)一步說明,讓我們最后來看看 JSON 的另一 用途: 作為配置文件放在某一個地方。
由于JSON具備廣泛的支持、服務(wù)端解析的便利性以及良好的可讀性,JSON 常用來存儲配置信息。
來比較一下使用 INI、XML、JSON 三種格式存儲同樣設(shè)置的區(qū)別
使用 INI 格式的某游戲配置文件(settings.ini)
[general] playIntro=false mouseSensitivity=0.54[display] complexTextures=true brightness=4.2 widgetsPerFrame=326 mode=windowed[sound]volume=1 effects=0.68
用 XML 格式的某游戲配置文件(settings.xml)
<?xml version="1.0" encoding="utf-8"?><settings> <general> <playIntro>false</playIntro> <mouseSensitivity>0.54</mouseSensitivity> </general> <display> <complexTextures>true</complexTextures> <brightness>4.2</brightness> <widgetsPerFrame>326</widgetsPerFrame> </display> <sound> <volume>1</volume> <effects>0.68</effects> </sound></settings>
使用 JSON 格式的某游戲配置文件(settings.json)
{ "general": { "playIntro": false, "mouseSensitivity": 0.54 }, "display": { "complexTextures": true, "brightness": 4.2, "widgetsPerFrame": 326, "mode": "windowed" }, "sound": { "volume": 1, "effects": 0.68 }}
這三種格式都有著良好的可讀性,而且修改起來也很方便。
每種格式都有優(yōu)點和缺點。
INI
格式?jīng)]有 XML 那些大于號和小于號以及 JSON 的花括號,所以有更好的可讀性。不過 INI 格式不能很好地表示更為復(fù)雜的信息,如嵌套信息或復(fù)雜的列表。
XML
能夠包含更為復(fù)雜的數(shù)據(jù), 但是它不像 JSON 一樣具有數(shù)據(jù)類型。
除了這些數(shù)據(jù)格式本身具有的優(yōu)缺點外,是否能夠很方便地被編程語言 / 框架解析也是一個很重要的考量因素。如果 JSON 解析器已經(jīng)在你的應(yīng)用中 深度使用了,那么 JSON 可能是你配置文件的最佳選擇。
現(xiàn)實中一個使用 JSON 作 為配置文件的極佳例子就是 Node.js 默認(rèn)的 JavaScript 包管理器:npm
。當(dāng) 然,它也被 AngularJS 和 jQuery 等其他框架使用。
npm
包管理器使用 JSON 作為配置文件,文件名稱為 package.json
。該文件 包含了每個包的具體信息,如名稱、版本、作者、貢獻(xiàn)者、依賴、腳本以 及許可:
npm 的 package.json 示例文件
{ "name": "bobatron", "version": "1.0.0", "description": "The extraordinary library of Bob.", "main": "bob.js", "scripts": { "prepublish": "coffee -o lib/ -c src/bob.coffee" }, "repository": { "type": "git", "url": "git://github.com/bobatron/bob.git" }, "keywords": [ "bob", "tron" ], "author": "Bob Barker <bob.barker@fakemail.com> (http://bob.com/)", "license": "BSD-3-Clause", "bugs": { "url": "https://github.com/bobatron/issues" }}
盡管 JSON 是作為配置文件放在一個地方的,但從某種意義上講,它還是 在扮演著數(shù)據(jù)交換格式的角色。對于存放在某處的配置文件來說,交換仍在人和計算機之間進(jìn)行著,而數(shù)據(jù)則在其中起著溝通作用。
無論是作為服務(wù)器上的配置文件,還是作為通過 URL 請求的資源,JSON 始終都在履行作為數(shù)據(jù)交換格式的職責(zé)。本書中,我們一步步探索了這些職責(zé)(或是角色)。讓我們最后概括一下 JSON 是如何在我們的生活中發(fā)揮作用的。
在服務(wù)端,對象可以被序列化為 JSON 格式的文本,并且通過反序列化 變回對象。服務(wù)端代碼可以請求 JSON。在第 9 章中,我們了解了如何用 ASP.NET 和 PHP 去實現(xiàn)這一功能。
此外,JSON 也可以被看作一種文本格式,用作一種面向文檔存儲類型的數(shù) 據(jù)庫的文檔。第 8 章介紹了使用此方法的 CouchDB。該數(shù)據(jù)庫同時提供了 可用于 HTTP Web API 的接口。
HTTP Web API 是一個對諸如 HTML 或 JSON 文檔等資源進(jìn)行請求和 響應(yīng)的系統(tǒng)。這些文檔使用 URL 經(jīng)由 HTTP 請求。第 6 章就講解了 OpenWeatherMap API 是如何利用這一點以 JSON 資源的形式提供天氣數(shù) 據(jù)的。
JavaScript XmlHttpRequest
可 以 通 過 URL 請 求 JSON 資 源。 為 了 在 JavaScript 代碼中使用 JSON,要先將它反序列化為 JSON 對象。JavaScript內(nèi)置的 JSON.parse()
函數(shù)能夠快速而有效地實現(xiàn)這一功能。
從 JavaScript 對象到 JSON,再到服務(wù)端的對象,能夠看到數(shù)據(jù)跑了一圈。 每一天,數(shù)據(jù)都在全世界各種系統(tǒng)中進(jìn)進(jìn)出出,它們的載體就是數(shù)據(jù)交換格式。
我們再次從高空俯瞰,可以發(fā)現(xiàn) JSON 并不是唯一一種數(shù)據(jù)交換格式。有 些數(shù)據(jù)以逗號分隔值(CSV)為載體,還有些是 Excel 表格。它們都是表格 化的。還有些數(shù)據(jù)以 XML 格式存在,這種格式支持?jǐn)?shù)據(jù)的嵌套。
數(shù)據(jù)有多種格式與形式。有多種不同的系統(tǒng)在不停地推送和接收著數(shù)據(jù)。 在考慮使用什么數(shù)據(jù)交換格式時,數(shù)據(jù)的形式和交換數(shù)據(jù)的系統(tǒng)都應(yīng)該被 考慮到。雖說本書青睞于 JSON,并對它進(jìn)行了全方位的介紹,但要記住, JSON 不總是最佳選擇
在互聯(lián)網(wǎng)瀏覽器這類通過 JavaScript 來支持面向?qū)ο缶幊痰南到y(tǒng)中,JSON 是較為理想的數(shù)據(jù)交換格式。在使用 JavaScript 與 Web API 交流或是創(chuàng)建 AJAX 交互時,JSON 都有極佳的表現(xiàn)。
對于那些諸如面向?qū)ο蟮姆?wù)端 Web 框架之類的系統(tǒng),JSON 依舊是理想 的數(shù)據(jù)交換格式。有了 JSON,數(shù)據(jù)就可以以對象字面量的形式在系統(tǒng)中進(jìn) 出,并保持它原有的結(jié)構(gòu)。
就像本章 10.1 節(jié)“作為配置文件的 JSON”所提到的例子那樣,選擇格式 前應(yīng)先考慮優(yōu)缺點。用對的工具,去做對的工作。
第一次嘗試將一本書籍內(nèi)容的概要摘選出來寫博文,這樣再摘選的過程中相當(dāng)于是閱讀書籍了,國光這樣嘗試了一下,發(fā)現(xiàn)閱讀效率還是很高的,一本書可以很快閱讀完。所以我又開了一個Books書籍分類的目錄,博客日后會大量更新相關(guān)書籍的摘要文章。