自: https://twobithistory.org/2017/09/21/the-rise-and-rise-of-json.html
作者: Two-bit History
譯者: runningwater
JSON 已經(jīng)占領(lǐng)了全世界。當(dāng)今,任何兩個(gè)應(yīng)用程序彼此通過(guò)互聯(lián)網(wǎng)通信時(shí),可以打賭它們?cè)谑褂?JSON。它已被所有大型企業(yè)所采用:十大最受歡迎的 web API 接口列表中(主要由 Google、Facebook 和 Twitter 提供),僅僅只有一個(gè) API 接口是以 XML 的格式開(kāi)放數(shù)據(jù)的。 1 這個(gè)列表中的 Twitter API 為此做了一個(gè)鮮活的注腳:其對(duì) XML 格式的支持到 2013 年結(jié)束,其時(shí)發(fā)布的新版本的 API 取消 XML 格式,轉(zhuǎn)而僅使用 JSON。JSON 也在程序編碼級(jí)別和文件存儲(chǔ)上被廣泛采用:在 Stack Overflow(LCTT 譯注:一個(gè)面向程序員的問(wèn)答網(wǎng)站)上,現(xiàn)在更多的是關(guān)于 JSON 的問(wèn)題,而不是其他的數(shù)據(jù)交換格式。 2
XML 仍然在很多地方存在。網(wǎng)絡(luò)上它被用于 SVG 和 RSS / Atom 信息流。Android 開(kāi)發(fā)者想要獲得用戶(hù)權(quán)限許可時(shí),需要在其 APP 的 manifest 文件中聲明 —— 此文件是 XML 格式的。XML 的替代品也不僅僅只有 JSON,現(xiàn)在有很多人在使用 YAML 或 Google 的 Protocol Buffers 等技術(shù),但這些技術(shù)的受歡迎程度遠(yuǎn)不如 JSON。目前來(lái)看,JSON 是應(yīng)用程序在網(wǎng)絡(luò)之間通信的首選協(xié)議格式。
考慮到自 2005 年來(lái) Web 編程世界就垂涎于 “異步 JavaScript 和 XML” 而非 “異步 JavaScript 和 JSON” 的技術(shù)潛力,你可以發(fā)現(xiàn) JSON 的主導(dǎo)地位是如此的讓人驚訝。當(dāng)然了,這可能與這兩種通信格式的受歡迎程度無(wú)關(guān),而僅反映出縮寫(xiě) “AJAX” 似乎比 “AJAJ” 更具吸引力。但是,即使在 2005 年有些人(實(shí)際上沒(méi)有太多人)已經(jīng)用 JSON 來(lái)取代 XML 了,我們不禁要問(wèn) XML 的噩運(yùn)來(lái)的如此之快,以至于短短十來(lái)年,“異步 JavaScript 和 XML” 這個(gè)名稱(chēng)就成為一個(gè)很諷刺的誤稱(chēng)。那這十來(lái)年發(fā)生了什么?JSON 怎么會(huì)在那么多應(yīng)用程序中取代了 XML?現(xiàn)在被全世界工程師和系統(tǒng)所使用、依賴(lài)的這種數(shù)據(jù)格式是誰(shuí)提出的?
2001 年 4 月,首個(gè) JSON 格式的消息被發(fā)送出來(lái)。此消息是從舊金山灣區(qū)某車(chē)庫(kù)的一臺(tái)計(jì)算機(jī)發(fā)出的,這是計(jì)算機(jī)歷史上重要的的時(shí)刻。Douglas Crockford 和 Chip Morningstar 是一家名為 State Software 的技術(shù)咨詢(xún)公司的聯(lián)合創(chuàng)始人,他們當(dāng)時(shí)聚集在 Morningstar 的車(chē)庫(kù)里測(cè)試某個(gè)想法,發(fā)出了此消息。
在 “AJAX” 這個(gè)術(shù)語(yǔ)被創(chuàng)造之前,Crockford 和 Morningstar 就已經(jīng)在嘗試構(gòu)建好用的 AJAX 應(yīng)用程序了,可是瀏覽器對(duì)其兼容性不好。他們想要在初始頁(yè)面加載后就將數(shù)據(jù)傳遞給應(yīng)用程序,但其目標(biāo)要針對(duì)所有的瀏覽器,這就實(shí)現(xiàn)不了。
這在今天看來(lái)不太可信,但是要記得 2001 年的時(shí)候 Internet Explorer(IE)代表了網(wǎng)頁(yè)瀏覽器的最前沿技術(shù)產(chǎn)品。早在 1999 年的時(shí)候,Internet Explorer 5 就支持了原始形式的 XMLHttpRequest,開(kāi)發(fā)者可以使用名為 ActiveX 的框架來(lái)訪問(wèn)此對(duì)象。Crockford 和 Morningstar 能夠使用此技術(shù)(在 IE 中)來(lái)獲取數(shù)據(jù),但是在 Netscape 4 中(這是他們想要支持的另一種瀏覽器)就無(wú)法使用這種解決方案。為此 Crockford 和 Morningstar 只得使用一套不同的系統(tǒng)以兼容不同的瀏覽器。
第一條 JSON 消息如下所示:
<html><head><script>
document.domain = 'fudco';
parent.session.receive(
{ to: "session", do: "test",
text: "Hello world" }
)
</script></head></html>
消息中只有一小部分類(lèi)似于今天我們所知的 JSON,本身其實(shí)是一個(gè)包含有一些 JavaScript 的 HTML 文檔。類(lèi)似于 JSON 的部分只是傳遞給名為 receive() 的函數(shù)的 JavaScript 對(duì)象字面量。
Crockford 和 Morningstar 決定濫用 HTML 的幀(<frame>)以發(fā)送數(shù)據(jù)。他們可以讓一個(gè)幀指向一個(gè)返回的上述 HTML 文檔的 URL。當(dāng)接收到 HTML 時(shí),JavaScript 代碼段就會(huì)運(yùn)行,就可以把數(shù)據(jù)對(duì)象字面量如實(shí)地傳遞回應(yīng)用程序。只要小心的回避瀏覽器保護(hù)策略(即子窗口不允許訪問(wèn)父窗口),這種技術(shù)就可以正常運(yùn)行無(wú)誤??梢钥吹?Crockford 和 Mornginstar 通過(guò)明確地設(shè)置文檔域這種方法來(lái)達(dá)到其目的。(這種基于幀的技術(shù),有時(shí)稱(chēng)為隱藏幀技術(shù),通常在 90 年代后期,即在廣泛使用 XMLHttpRequest 技術(shù)之前使用。 3)
第一個(gè) JSON 消息的驚人之處在于,它顯然不是一種新的數(shù)據(jù)格式的首次使用。它就是 JavaScript!實(shí)際上,以此方式使用 JavaScript 的想法如此簡(jiǎn)單,Crockford 自己也說(shuō)過(guò)他不是第一個(gè)這樣做的人。他說(shuō) Netscape 公司的某人早在 1996 年就使用 JavaScript 數(shù)組字面量來(lái)交換信息。 4 因?yàn)橄⒕褪?JavaScript,其不需要任何特殊解析工作,JavaScript 解釋器就可搞定一切。
最初的 JSON 信息實(shí)際上與 JavaScript 解釋器發(fā)生了沖突。JavaScript 保留了大量的關(guān)鍵字(ECMAScript 6 版本就有 64 個(gè)保留字),Crockford 和 Morningstar 無(wú)意中在其 JSON 中使用了一個(gè)保留字。他們使用了 do 作為了鍵名,但 do 是解釋器中的保留字。因?yàn)?JavaScript 使用的保留字太多了,Crockford 做了決定:既然不可避免的要使用到這些保留字,那就要求所有的 JSON 鍵名都加上引號(hào)。被引起來(lái)的鍵名會(huì)被 JavaScript 解釋器識(shí)別成字符串,其意味著那些保留字也可以放心安全的使用。這就為什么今天 JSON 鍵名都要用引號(hào)引起來(lái)的原因。
Crockford 和 Morningstar 意識(shí)到這技術(shù)可以應(yīng)用于各類(lèi)應(yīng)用系統(tǒng)。想給其命名為 “JSML”,即JavaScript 標(biāo)記語(yǔ)言(JavaScript Markup Language)的意思,但發(fā)現(xiàn)這個(gè)縮寫(xiě)已經(jīng)被一個(gè)名為 Java Speech 標(biāo)記語(yǔ)言的東西所使用了。因此他們決定采用 “JavaScript Object Notation”,縮寫(xiě)為 JSON。他們開(kāi)始向客戶(hù)推銷(xiāo),但很快發(fā)現(xiàn)客戶(hù)不愿意冒險(xiǎn)使用缺乏官方規(guī)范的未知技術(shù)。所以 Crockford 決定寫(xiě)一個(gè)規(guī)范。
2002 年,Crockford 買(mǎi)下了 JSON.org 域名,放上了 JSON 語(yǔ)法及一個(gè)解釋器的實(shí)例例子。該網(wǎng)站仍然在運(yùn)行,現(xiàn)在已經(jīng)包含有 2013 年正式批準(zhǔn)的 JSON ECMA 標(biāo)準(zhǔn)的顯著鏈接。在該網(wǎng)站建立后,Crockford 并沒(méi)有過(guò)多的推廣,但很快發(fā)現(xiàn)很多人都在提交各種不同編程語(yǔ)言的 JSON 解析器實(shí)現(xiàn)。JSON 的血統(tǒng)顯然與 JavaScript 相關(guān)聯(lián),但很明顯 JSON 非常適合于不同語(yǔ)言之間的數(shù)據(jù)交換。
2005 年,JSON 有了一次大爆發(fā)。那一年,一位名叫 Jesse James Garrett 的網(wǎng)頁(yè)設(shè)計(jì)師和開(kāi)發(fā)者在博客文章中創(chuàng)造了 “AJAX” 一詞。他很謹(jǐn)慎地強(qiáng)調(diào):AJAX 并不是新技術(shù),而是 “好幾種蓬勃發(fā)展的技術(shù)以某種強(qiáng)大的新方式匯集在一起。 5 ” AJAX 是 Garrett 給這種正受到青睞的 Web 應(yīng)用程序的新開(kāi)發(fā)方法的命名。他的博客文章接著描述了開(kāi)發(fā)人員如何利用 JavaScript 和 XMLHttpRequest 構(gòu)建新型應(yīng)用程序,這些應(yīng)用程序比傳統(tǒng)的網(wǎng)頁(yè)更具響應(yīng)性和狀態(tài)性。他還以 Gmail 和 Flickr 網(wǎng)站已經(jīng)使用 AJAX 技術(shù)作為了例子。
當(dāng)然了,“AJAX” 中的 “X” 代表 XML。但在隨后的問(wèn)答帖子中,Garrett 指出,JSON 可以完全替代 XML。他寫(xiě)道:“雖然 XML 是 AJAX 客戶(hù)端進(jìn)行數(shù)據(jù)輸入、輸出的最完善的技術(shù),但要實(shí)現(xiàn)同樣的效果,也可以使用像 JavaScript Object Notation(JSON)或任何類(lèi)似的結(jié)構(gòu)數(shù)據(jù)方法等技術(shù)。 ” 6
開(kāi)發(fā)者確實(shí)發(fā)現(xiàn)在構(gòu)建 AJAX 應(yīng)用程序時(shí)可以很容易的使用 JSON,許多人更喜歡它而不是 XML。具有諷刺意味的是,對(duì) AJAX 的興趣逐漸的導(dǎo)致了 JSON 的普及。大約在這個(gè)時(shí)候,JSON 引起了博客圈的注意。
2006 年,Dave Winer,一位高產(chǎn)的博主,他也是許多基于 XML 的技術(shù)(如 RSS 和 XML-RPC)背后的開(kāi)發(fā)工程師,他抱怨到 JSON 毫無(wú)疑問(wèn)的正在重新發(fā)明 XML。盡管人們認(rèn)為數(shù)據(jù)交換格式之間的競(jìng)爭(zhēng)不會(huì)導(dǎo)致某一技術(shù)的消亡。其寫(xiě)到:
毫無(wú)疑問(wèn),我可以編寫(xiě)一個(gè)例程來(lái)解析 JSON,但來(lái)看看他們要重新發(fā)明一個(gè)東西有多大的意義,出于某種原因 XML 本身對(duì)他們來(lái)說(shuō)還不夠好(我很想聽(tīng)聽(tīng)原因)。誰(shuí)想干這荒謬之事?查找一棵樹(shù)然后把節(jié)點(diǎn)串起來(lái)。可以立馬試試。 7
我很理解 Winer 的挫敗感。事實(shí)上并沒(méi)有太多人喜歡 XML。甚至 Winer 也說(shuō)過(guò)他不喜歡 XML。 8 但 XML 已被設(shè)計(jì)成一個(gè)可供任何人使用,并且可以用于幾乎能想象到的所有事情。歸根到底,XML 實(shí)際上是一門(mén)元語(yǔ)言,允許你為特定應(yīng)用程序自定義該領(lǐng)域特定的語(yǔ)言。如 Web 信息流技術(shù) RSS 和 SOAP(簡(jiǎn)單對(duì)象訪問(wèn)協(xié)議)就是例子。Winer 認(rèn)為由于通用交換格式所帶來(lái)的好處,努力達(dá)成共識(shí)非常重要。XML 的靈活性應(yīng)該能滿(mǎn)足任何人的需求,然而 JSON 格式呢,其并沒(méi)有比 XML 提供更多東西,除了它拋棄了使 XML 更靈活的那些繁瑣的東西。
Crockford 閱讀了 Winer 的這篇文章并留下了評(píng)論。為了回應(yīng) JSON 重新發(fā)明 XML 的指責(zé),Crockford 寫(xiě)到:“重造輪子的好處是可以得到一個(gè)更好的輪子?!?sup> 9
到 2014 年,JSON 已經(jīng)由 ECMA 標(biāo)準(zhǔn)和 RFC 官方正式認(rèn)可。它有了自己的 MIME 類(lèi)型。JSON 已經(jīng)進(jìn)入了大聯(lián)盟時(shí)代。
為什么 JSON 比 XML 更受歡迎?
在 JSON.org 網(wǎng)站上,Crockford 總結(jié)了一些 JSON 的優(yōu)勢(shì)。他寫(xiě)到,JSON 的語(yǔ)法極少,其結(jié)構(gòu)可預(yù)測(cè),因此 JSON 更容易被人類(lèi)和機(jī)器理解。 10 其他博主一直關(guān)注 XML 的冗長(zhǎng)啰嗦及“尖括號(hào)負(fù)擔(dān)”。 11XML 中每個(gè)開(kāi)始標(biāo)記都必須與結(jié)束標(biāo)記匹配,這意味著 XML 文檔包含大量的冗余信息。在未壓縮時(shí),XML 文檔的體積比同等信息量 JSON 文檔的體積大很多,但是,更重要的,這也使 XML 文檔更難以閱讀。
Crockford 還聲稱(chēng) JSON 的另一個(gè)巨大優(yōu)勢(shì)是其被設(shè)計(jì)為數(shù)據(jù)交換格式。 12 從一開(kāi)始,它的目的就是在應(yīng)用程序間傳遞結(jié)構(gòu)化信息的。而 XML 呢,雖然也可以使用來(lái)傳遞數(shù)據(jù),但其最初被設(shè)計(jì)為文檔標(biāo)記語(yǔ)言。它演變自 SGML(通用標(biāo)準(zhǔn)標(biāo)記語(yǔ)言),而它又是從被稱(chēng)為 Scribe 的標(biāo)記語(yǔ)言演變而來(lái),其旨在發(fā)展成類(lèi)似于 LaTeX 一樣的文字處理系統(tǒng)。XML 中,一個(gè)標(biāo)簽可以包含有所謂的“混合內(nèi)容”,即帶有圍繞單詞、短語(yǔ)的內(nèi)嵌標(biāo)簽的文本。這會(huì)讓人浮現(xiàn)出一副用紅藍(lán)筆記錄的手稿畫(huà)面,這是標(biāo)記語(yǔ)言核心思想的形象比喻。另一方面,JSON 不支持對(duì)混合內(nèi)容模型的清晰構(gòu)建,但這也意味著它的結(jié)構(gòu)足夠簡(jiǎn)單。一份文檔最好的建模就是一棵樹(shù),但 JSON 拋棄了這種文檔的思想,Crockford 將 JSON 抽象限制為字典和數(shù)組,這是所有程序員構(gòu)建程序時(shí)都會(huì)使用的最基本的、也最熟悉的元素。
最后,我認(rèn)為人們不喜歡 XML 是因?yàn)樗屓死Щ?。它讓人迷惑的地方就是有很多不同的風(fēng)格。乍一看,XML 本身及其子語(yǔ)言(如 RSS、ATOM、SOAP 或 SVG)之間的界限并不明顯。典型的 XML 文檔的第一行標(biāo)識(shí)了該 XML 的版本,然后該 XML 文檔應(yīng)該符合特定的子語(yǔ)言。這就有變化需要考慮了,特別是跟 JSON 做比較,JSON 是如此簡(jiǎn)單,以至于永遠(yuǎn)不需要編寫(xiě)新版本的 JSON 規(guī)范。XML 的設(shè)計(jì)者試圖將 XML 做為唯一的數(shù)據(jù)交換格式以支配所有格式,就會(huì)掉入那個(gè)經(jīng)典的程序員陷阱:過(guò)度工程化。XML 非?;\統(tǒng)及概念化,所以很難于簡(jiǎn)單的使用。
在 2000 年的時(shí)候,發(fā)起了一場(chǎng)使 HTML 符合 XML 標(biāo)準(zhǔn)的活動(dòng),發(fā)布了一份符合 XML 標(biāo)準(zhǔn)的 HTML 開(kāi)發(fā)規(guī)范,這就此后很出名的 XHTML。雖然一些瀏覽器廠商立即開(kāi)始支持這個(gè)新標(biāo)準(zhǔn),但也很明顯,大部分基于 HTML 技術(shù)的開(kāi)發(fā)者不愿意改變他們的習(xí)慣。新標(biāo)準(zhǔn)要求對(duì) XHTML 文檔進(jìn)行嚴(yán)格的驗(yàn)證,而不是基于 HTML 的基準(zhǔn)。但大多的網(wǎng)站都是依賴(lài)于 HTML 的寬容規(guī)則的。到 2009 年的時(shí)候,試圖編寫(xiě)第二版本的 XHTML 標(biāo)準(zhǔn)的努力已經(jīng)流產(chǎn),因?yàn)槲磥?lái)已清晰可見(jiàn),HTML 將會(huì)發(fā)展為 HTML5(一種不強(qiáng)制要求接受 XML 規(guī)則的標(biāo)準(zhǔn))。
如果 XHTML 的努力取得了成功,那么 XML 可能會(huì)成為其設(shè)計(jì)者所希望的通用數(shù)據(jù)格式。想象一下,一個(gè) HTML 文檔和 API 響應(yīng)具有完全相同結(jié)構(gòu)的世界。在這樣的世界中,JSON 可能不會(huì)像現(xiàn)在一樣普遍存在。但我把 HTML 的失敗看做是 XML 陣營(yíng)的一種道義上的失敗。如果 XML 不是 HTML 的最佳工具,那么對(duì)于其他應(yīng)用程序,也許會(huì)有更好的工具出現(xiàn)。在這個(gè)世界,我們的世界,很容易看到像 JSON 格式這樣的足夠簡(jiǎn)單、量體裁衣的才能獲得更大的成功。
如果你喜歡這博文,每?jī)芍軙?huì)更新一次! 請(qǐng)?jiān)?Twitter 上關(guān)注 @TwoBitHistory 或訂閱 RSS feed ,以確保得到更新的通知。
via: https://twobithistory.org/2017/09/21/the-rise-and-rise-of-json.html
作者: Two-Bit History 選題: lujun9972 譯者: runningwater 校對(duì): wxy
本文由 LCTT 原創(chuàng)編譯, Linux中國(guó) 榮譽(yù)推出
者:andruzhang,騰訊IEG后臺(tái)開(kāi)發(fā)工程師
| 導(dǎo)語(yǔ) JSON,一個(gè)偉大的協(xié)議,前端工程師的卓越發(fā)明!相信 99% 的程序員都認(rèn)識(shí) JSON。但這么簡(jiǎn)單的 JSON,也有坑!相信我,看完這篇文章,你就可以吃透這個(gè)可愛(ài)的 JSON 了。
相信 99% 的程序員都認(rèn)識(shí) JSON,它作為前后端交互的熱門(mén)協(xié)議,因其易理解、簡(jiǎn)單、靈活和超強(qiáng)的可讀性,得到了互聯(lián)網(wǎng)的廣泛歡迎,甚至很多微服務(wù)之間的傳輸協(xié)議中也得到應(yīng)用。
但是筆者在開(kāi)發(fā)一個(gè) Go 的 JSON 編解碼庫(kù)的過(guò)程中,除了自己趟過(guò)各種奇奇怪怪的問(wèn)題之外,也認(rèn)識(shí)到廣大程序員們對(duì) JSON 各種奇奇怪怪的用法和使用姿勢(shì)。在處理解決這些問(wèn)題之后,筆者萌生了對(duì) JSON 進(jìn)行進(jìn)一步科普和介紹的想法。
相信我,看完這篇文章,你就可以吃透這個(gè)可愛(ài)的 JSON 了。
這不是萬(wàn)字長(zhǎng)文,所以答應(yīng)我,不要 TLDR(too long, don't read) 好嗎?
這個(gè)問(wèn)題似乎很容易回答:JavaScript Object Notation,直譯就是 JavaScript 對(duì)象表示。
然而,這個(gè)命名中的 “JavaScript” 是個(gè)很大的誤導(dǎo),讓人以為 JSON 是附屬于 JavaScript 的。其實(shí)不然,JSON 是完全獨(dú)立于任何語(yǔ)言之上的一個(gè)對(duì)象表示協(xié)議,甚至從我個(gè)人的角度來(lái)說(shuō),它非常的不 “JS”。
從大家的認(rèn)知中,相信以下的幾點(diǎn)是常識(shí):
好了,懂了上面幾點(diǎn),其實(shí)也就弄懂了 JSON 90% 甚至是 99% 的應(yīng)用場(chǎng)景了。程序員們也足以可以實(shí)現(xiàn)簡(jiǎn)單的 JSON 編碼邏輯。
如果你想知道剩下那些讓人掉大牙的 1%,歡迎你往下看;如果你想要自己開(kāi)發(fā)一個(gè) JSON 編解碼庫(kù),以下內(nèi)容也能夠讓你少走很多彎路:
在了解各種 JSON 的坑之前,我們先來(lái)了解一下 JSON 標(biāo)準(zhǔn)本身。
現(xiàn)行通行的 JSON 標(biāo)準(zhǔn)是 ECMA-404(也可參見(jiàn)附件),這篇協(xié)議總共有14頁(yè),但除去封面、封底、目錄、簡(jiǎn)介、版權(quán)聲明,正文只有5頁(yè),并且其中3頁(yè)大部分是圖片。所以筆者推薦所有的程序員都把這篇文檔通讀一遍,恐怕這是大部分人唯一能完整讀完的主流協(xié)議了(狗頭)。
所以啊,“可愛(ài)” 的 JSON 可真不是標(biāo)題黨——試想這么短小的協(xié)議,怎不可謂可愛(ài)呢!
通讀了文檔之后我們可以發(fā)現(xiàn),除了前文提及的幾個(gè)常識(shí)之外,下面有幾個(gè)知識(shí)點(diǎn)估計(jì)大家很少留意:
我們解讀一下上面這些知識(shí)點(diǎn)帶來(lái)的影響:
JSON 傳輸?shù)膽?yīng)當(dāng)是可視化字符,而不應(yīng)該也無(wú)法承載不可讀的二進(jìn)制數(shù)據(jù)
換句話說(shuō),請(qǐng)盡量不要用 JSON 來(lái)傳輸二進(jìn)制數(shù)據(jù)
這個(gè)問(wèn)題不大,主要是對(duì)各種庫(kù)的使用上——有些庫(kù)提供了 boolean 類(lèi)型分類(lèi),而有些庫(kù)則按照標(biāo)準(zhǔn)協(xié)議分為 true 和 false 兩種類(lèi)型,請(qǐng)注意區(qū)分
其實(shí)這影響不大,但是這使得 JSON 多了一個(gè)額外功能: 當(dāng)我們要把包含換行符的文本壓在一行內(nèi),但又要保持高可讀性的時(shí)候,我們可以將文本序列化為 JSON
這個(gè)特性在打日志的時(shí)候特別有用
這主要是在解析 JSON 數(shù)據(jù)時(shí),需要注意兼容
這個(gè)問(wèn)題可大可小,大部分情況下不會(huì)遇到,但是一旦出現(xiàn)了,會(huì)導(dǎo)致整個(gè)序列化過(guò)程失敗。開(kāi)發(fā)者們需要謹(jǐn)慎處理浮點(diǎn)數(shù),下文會(huì)進(jìn)一步提及
為什么列出這幾點(diǎn)?讓我們繼續(xù)往下看:
JSON 如此簡(jiǎn)單,但也正因?yàn)樗奶匦裕覀儠?huì)不知不覺(jué)地落入一些圈套中。下面我列幾個(gè)常見(jiàn)的坑,讀者看看能不能對(duì)號(hào)入座:
前面說(shuō)到,JSON 明確聲明自己是用于承載 unicode 的。但是,unicode 除了規(guī)定每個(gè)字符碼的含義(碼點(diǎn))之外,還包含另外一個(gè)重要規(guī)范,那就是如何將這些字符串成字符流,這就是我們常說(shuō)的 UTF-8、UTF-16BE、 UTF-16LE 等等概念。而 JSON 并沒(méi)有對(duì)此作明確限定。這就導(dǎo)致了在 JSON 的編碼與解碼端,如果沒(méi)有約定好,那么就會(huì)出現(xiàn)亂碼。
筆者曾經(jīng)與一個(gè)合作伙伴的開(kāi)發(fā)工程師對(duì)接過(guò) JSON,對(duì)方使用 Java 解碼我發(fā)出的原始數(shù)據(jù)時(shí)出現(xiàn)亂碼。我告訴對(duì)方,應(yīng)該用 UTF-8 格式解碼,但是對(duì)方不明白 UTF-8 是什么,只是不停的告訴我他使用的是哪一個(gè) Java 函數(shù)。
我的解決方案不敢說(shuō)萬(wàn)能,但應(yīng)該即便是上古的解碼器都能處理——這個(gè)方案就是指定各編碼器在編碼時(shí),對(duì)大于 ASCII 范圍的字符均作轉(zhuǎn)義處理為 \uXXXX 格式。這么一通操作后,我的合作伙伴表示:程序通了。
其實(shí)在 JSON 規(guī)范中,列舉了不少篇幅說(shuō)明大于 U+00FF 的碼點(diǎn)應(yīng)該如何轉(zhuǎn)義,包括大于 U+FFFF 的。所以從筆者的個(gè)人觀點(diǎn)看來(lái),如果我們嚴(yán)格按照 JSON 規(guī)范的話,什么 UTF-8,GB18030 等編碼格式都是未被允許的,唯一嚴(yán)格允許的就是 \uXXXX 轉(zhuǎn)義。但是在實(shí)際操作中,這種轉(zhuǎn)義太浪費(fèi)字節(jié)序列了,各種語(yǔ)言對(duì) string 類(lèi)型進(jìn)行操作時(shí),習(xí)慣性地按照本身的字符串在內(nèi)存中的默認(rèn)編碼格式照搬到 JSON 序列化上了。
如果 JSON 的編碼端無(wú)法確?;騾f(xié)調(diào)對(duì)端解碼器的編碼格式,那么請(qǐng)統(tǒng)一使用 \uXXXX 轉(zhuǎn)義。
如果讀者不需要自行編碼或解析 JSON 數(shù)據(jù)的話,可以跳過(guò)這一小節(jié);否則,這一段是必修課。
對(duì)于編碼值大于 127 的字符來(lái)說(shuō),如上文所示,我們可以轉(zhuǎn)義為 \uXXXX 格式。那么是不是直接寫(xiě) sprintf("\u04X", aByte) 就可以呢?
如果你這么做,那么作為一個(gè)通用庫(kù)來(lái)說(shuō)……
嚴(yán)格來(lái)說(shuō),\uXXXX 其實(shí)是對(duì) UTF-16 編碼的轉(zhuǎn)寫(xiě)。這是一個(gè)比較少用的編碼格式。我們都知道,UTF-8 是一個(gè)變長(zhǎng)的編碼格式,它編碼的基本單位是 1 個(gè)字節(jié)。受早期 Windows 16位 wchar 的影響,有些人可能會(huì)誤以為UTF-16 是定長(zhǎng)的 2 個(gè)字節(jié)。其實(shí)并不然,對(duì)于大于 65535 的 unicode 碼點(diǎn),UTF-16 使用 4 個(gè)字節(jié)編碼,而 JSON 只需要將編碼后的兩個(gè)半字(half world)按順序使用 \uXXXX 轉(zhuǎn)寫(xiě)出來(lái)就可以了。
對(duì) JSON 具體需要轉(zhuǎn)義的字符,以及 UTF-16 的相關(guān)內(nèi)容,筆者之前也寫(xiě)過(guò)一篇文章專(zhuān)門(mén)說(shuō)明,歡迎移步。
按理說(shuō),JSON 只應(yīng)該承載可見(jiàn)字符。但是按照 JSON 的規(guī)范,JSON 承載的是 unicode,而 ASCII 控制字符也是 unicode 的一部分,所以 JSON 也是可以承載 ASCII 控制字符的。
其實(shí)這個(gè)問(wèn)題并不大,即便把這些控制字符原封不動(dòng)地包裝在 JSON 序列化之后的數(shù)據(jù)流中,對(duì)端也是可以正確解析出來(lái)的。大家要注意的是,如果帶控制字符的話,數(shù)據(jù)渲染到終端時(shí),某些控制字符可能不會(huì)被渲染出來(lái)。如果此時(shí)你從終端復(fù)制一段數(shù)據(jù),在粘貼到別處,這些字符可能就都丟失了。
眾所周知,在許多語(yǔ)言的內(nèi)部處理邏輯中,帶小數(shù)部分的數(shù)字是使用浮點(diǎn)數(shù)來(lái)處理的。對(duì)于小數(shù)部分無(wú)法被 2 除盡的十進(jìn)制數(shù),系統(tǒng)(為了照顧 “你們?nèi)祟?lèi)”)而使用二進(jìn)制浮點(diǎn)數(shù)的近似值來(lái)表示。
具體到 JSON 中,坑在哪里?其實(shí)吧這里不算是 JSON 的坑,而是一個(gè)通用的問(wèn)題。我簡(jiǎn)單提一下吧:
首先我們知道,對(duì)很多強(qiáng)類(lèi)型語(yǔ)言來(lái)說(shuō),浮點(diǎn)數(shù)往往可以細(xì)分為單精度和雙精度兩種,前者使用 4 個(gè)字節(jié),后者使用 8 個(gè)字節(jié)。單精度在有效位數(shù)方面比雙精度數(shù)小一大截,但是在具體實(shí)踐中,考慮到數(shù)據(jù)傳輸、計(jì)算效率、數(shù)值范圍,往往單精度就足矣。
這個(gè)時(shí)候,如果一個(gè)浮點(diǎn)數(shù)在系統(tǒng)內(nèi)部經(jīng)過(guò)各種不同精度的轉(zhuǎn)換之后,在轉(zhuǎn)換成 JSON 時(shí)會(huì)有什么問(wèn)題呢?我們來(lái)考慮一下的過(guò)程:
此時(shí),我們會(huì)得到什么數(shù)字呢?根據(jù)不同的語(yǔ)言,輸出可能會(huì)不同。如果不指定精度的話,很多 JSON 編碼庫(kù)是支持根據(jù)浮點(diǎn)數(shù)的具體數(shù)值,猜測(cè)并且格式化為一個(gè)最接近的十進(jìn)制小數(shù)。以 Go 為例子,我們會(huì)發(fā)現(xiàn)通過(guò) JSON 輸出的時(shí)候,這個(gè) 2.1 變成了 2.0999999046325684。
這在本質(zhì)上,是因?yàn)閱尉葦?shù)經(jīng)過(guò)一次類(lèi)型轉(zhuǎn)換為雙精度后,其二進(jìn)制有效位數(shù)以零填充,轉(zhuǎn)為十進(jìn)制時(shí),對(duì)于雙精度浮點(diǎn)數(shù),這就不再是雙精度有效數(shù)字下的 2.1 了。
換句話說(shuō),開(kāi)發(fā)者們?cè)谔幚砀↑c(diǎn)數(shù)時(shí),需要考慮不同精度浮點(diǎn)數(shù)的精度處理差異,特別是金融相關(guān)的數(shù)據(jù)計(jì)算和傳輸,一不小心就會(huì)造成大量的對(duì)賬錯(cuò)誤。
前文提及,JSON 明確說(shuō)明不支持 +/-Inf 和 NaN 這兩組在 IEEE 754 中規(guī)定的特殊數(shù)值。但有一些數(shù)學(xué)運(yùn)算庫(kù),在計(jì)算之后會(huì)將奇點(diǎn)輸出為 +/-Inf 或 NaN,對(duì)于很多 JSON 編碼庫(kù)來(lái)說(shuō),遇到這種數(shù)值會(huì)導(dǎo)致整個(gè)數(shù)據(jù)編碼失敗。因此開(kāi)發(fā)者需要針對(duì)這種情況特殊處理。我開(kāi)發(fā)的 jsonvalue 中就有這樣的一個(gè)專(zhuān)題。畢竟是筆者在實(shí)際操作中趟過(guò)的坑……
在 JSON 規(guī)范中,明確強(qiáng)調(diào) array 類(lèi)型的子值順序的重要性(這很好理解)。
但是針對(duì) object 類(lèi)型,key 的順序則未提及。在實(shí)際操作中我發(fā)現(xiàn)不少應(yīng)用場(chǎng)景中把 object 的 K-V 也當(dāng)作有序數(shù)據(jù)來(lái)操作了——這在很多自己使用代碼簡(jiǎn)單拼接 JSON 串的場(chǎng)景中,出乎意料地很常見(jiàn)。
還請(qǐng)各位明確注意:JSON 的 object,我們應(yīng)當(dāng)默認(rèn)它是無(wú)序的。如果需要傳遞一系列有序的 KV 對(duì),那么請(qǐng)務(wù)必使用 array 類(lèi)型,不要再用 object 了,這絕對(duì)不是一個(gè)通用的做法。
在這一點(diǎn)上,我自己也犯過(guò)一個(gè)很低級(jí)的錯(cuò)誤:
年少無(wú)知的我有一次設(shè)計(jì)過(guò)一個(gè)模塊,接收上游發(fā)來(lái)的各種事件信息。為了確保事件都被處理,因此當(dāng)下游響應(yīng)不及時(shí)時(shí),上游可能會(huì)將同一事件重復(fù)發(fā)出。此時(shí)我需要對(duì)事件進(jìn)行冪等計(jì)算,確保同一事件不會(huì)被重復(fù)處理。
一開(kāi)始我這是簡(jiǎn)單對(duì)上游數(shù)據(jù)進(jìn)行 hash 計(jì)算。但是在實(shí)際運(yùn)作了一段時(shí)間,出了 bug,而原因也很簡(jiǎn)單,我們看看下面兩段數(shù)據(jù):
這兩段數(shù)據(jù)僅僅是 key 的順序不同而已,但如果使用上面的邏輯,這數(shù)據(jù)根本就是一模一樣的!我們永遠(yuǎn)要注意:如果我們沒(méi)有明確地與上游約定好的話,那么請(qǐng)永遠(yuǎn)不要對(duì)上游做任何假設(shè);即便使用文檔約束,也依然要多多檢查各種例外情況。
結(jié)果怎么解決?約束上游?這豈不是顯得我能力不行嘛(狗頭,主要是不想讓上游知道我的 bug 這么 low),所以我在我自己這邊簡(jiǎn)單對(duì)解析出來(lái)的 key 排序之后(反正 key 不多且無(wú)嵌套),再重新計(jì)算 hash 來(lái)解決。
本文從 JSON 標(biāo)準(zhǔn)出發(fā),結(jié)合自己的一些工作經(jīng)驗(yàn),整理了 JSON 編解碼過(guò)程中的一些坑和注意點(diǎn)。如果本文有謬誤,還請(qǐng)不吝指正;如果讀者還遇到了其他的坑,也歡迎補(bǔ)充。
此外,如果讀者中有 Go 開(kāi)發(fā)者的話,也歡迎了解一下我的 jsonvalue 庫(kù),點(diǎn)個(gè) star 或者給我提 issue 都非常歡迎~~
JSON 相關(guān)資料:
在線實(shí)例
通過(guò)我們的編輯器,您可以在線編輯 JavaScript 代碼,然后通過(guò)點(diǎn)擊一個(gè)按鈕來(lái)查看結(jié)果:
JSON 實(shí)例
<!DOCTYPEhtml><html><head><metacharset="utf-8"><title>菜鳥(niǎo)教程(runoob.com)</title></head><body><h2>JavaScript 創(chuàng)建 JSON 對(duì)象</h2><p>網(wǎng)站名稱(chēng): <spanid="jname"></span><br/> 網(wǎng)站地址: <spanid="jurl"></span><br/> 網(wǎng)站 slogan: <spanid="jslogan"></span><br/></p><script>
varJSONObject= {"name":"菜鳥(niǎo)教程", "url":"www.runoob.com", "slogan":"學(xué)的不僅是技術(shù),更是夢(mèng)想!"};document.getElementById("jname").innerHTML=JSONObject.namedocument.getElementById("jurl").innerHTML=JSONObject.urldocument.getElementById("jslogan").innerHTML=JSONObject.slogan
</script></body></html>
與 XML 相同之處
JSON 是純文本
JSON 具有"自我描述性"(人類(lèi)可讀)
JSON 具有層級(jí)結(jié)構(gòu)(值中存在值)
JSON 可通過(guò) JavaScript 進(jìn)行解析
JSON 數(shù)據(jù)可使用 AJAX 進(jìn)行傳輸
與 XML 不同之處
沒(méi)有結(jié)束標(biāo)簽
更短
讀寫(xiě)的速度更快
能夠使用內(nèi)建的 JavaScript eval() 方法進(jìn)行解析
使用數(shù)組
不使用保留字
為什么使用 JSON?
對(duì)于 AJAX 應(yīng)用程序來(lái)說(shuō),JSON 比 XML 更快更易使用:
使用 XML
讀取 XML 文檔
使用 XML DOM 來(lái)循環(huán)遍歷文檔
讀取值并存儲(chǔ)在變量中
使用 JSON
讀取 JSON 字符串
用 eval() 處理 JSON 字符串
如您還有不明白的可以在下面與我留言或是與我探討QQ群308855039,我們一起飛!
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。