使用 JSONObject 須要注意避免的一個問題

做者介紹:鮑協浩,小米MIUI部門, MIUI基礎應用組通信錄開發負責人html

問題現象

在 Android 業務同步的邏輯代碼中,使用到了 JSONObject 來解析服務端的 JSON 數據。同時本地由於業務新增需求的緣故,在本地數據庫中使用 JSONObject 緩存了包括水位等同步相關的信息,其中,水位值是 Long 型。但近期發現同步過程當中下一次同步時,傳遞給服務器的水位並非上一次服務器返回的新水位,而是相差一些。以 301028292893495297L 爲例,服務器返回這個水位以後,下次客戶端上傳的水位是 301028292893495296L,差值爲 -1。數據庫

問題排查

經過反覆排查代碼邏輯,發現水位從服務端返回到下次請求之間,只通過了如下轉換:segmentfault

圖片描述

認真閱讀代碼不難發現,Long 型的水位值保存在 JSON 對象中的時候轉成了 String 型,而在讀取的時候又看成是 Long 型來處理。所以會有精度缺失的問題,參見以下 JSONObject 的文檔:緩存

因而可知,在讀取 JSON 對象的某個值時,若是原先是 String 型,讀取的時候看成是 Long 型,是會將 String 型經過 Double 進行解析的,因此在值超過 2^52 時會有精度缺失的問題。因而,遇到的問題就能夠解釋了。如下是 Double 的存儲格式規範:服務器

圖片描述

其中,Double 和 Long 的精度測試代碼很簡單(輸入參數能夠提供例如 301028292893495297L 這樣超過 2^52 的 long 值,會發現其返回值不爲 0):測試

圖片描述

Double 和 Long 的精度測試代碼很簡單(輸入參數能夠提供例如 301028292893495297L 這樣超過 2^52 的 long 值):spa

知道了問題的根源,修復就一目瞭然了,在水位保存在 JSONObject 對象中時,應該看成 Long 型而不是 String 型來保存;亦或者在讀取的時候也看成是 String 型,而後經過 Long.valueOf 等接口進行解析。設計

另外,關於 JSON 對象中的值是 Long 型仍是 String 型,其實比較容易被忽略。若是JSON 對象在使用 String 表示的時候,該值對應處有引號就是 String 型。看以下的試用例就一目瞭然了:htm

圖片描述

相似的問題在網上隨意一搜,其實有許多人遇坑了,好比這個。對象

因此,儘管不能說這個庫的設計是很失敗的,但確定不算是一個設計良好的庫。由於你沒法直接從 API 名稱看出其內在的潛在邏輯,容易致使使用者使用不當。所以,經驗教訓就是:使用第三方庫的時候,能看 API 文檔就看 API 文檔,切不可望文生義。固然,這個問題可能也僅限在 Android 中較老的代碼模塊,畢竟新的代碼都會使用 GSON 等類庫進行 JSON 對象操做,也就不容易出現這樣的不易發現的問題了。

固然,單就這個問題來看,實際上是在新增業務邏輯的時候,沒有正確使用 JSONObject 對象的接口,Long 型的值不該當當作是 String 型進行保存而又當成是 Long 型來讀取,若是保存和讀取的接口保持對應,也就不會出現問題了。無論怎麼說,該問題的教訓是在使用 JSONObject 相關接口時要倍加當心謹慎。

備註:Github 上最新的 JSON-Java 庫沒有這個問題,能夠放心使用。

圖片描述

問題解決

知道了問題的根源,修復就一目瞭然了,在水位保存在 JSON 對象中時,應該看成 Long 型而不是 String 型來保存;或者在讀取的時候也看成是 String 型,而後經過 Long.valueOf 等接口進行解析。

問題後話

相似的問題在網上隨意一搜,其實有許多人遇坑了,好比這個。因此,儘管不能說這個庫的設計是很失敗的,但確定不算是一個設計良好的庫。由於你沒法直接從 API 名稱看出內在的潛在邏輯,致使使用不當。所以,經驗教訓就是:使用第三方庫的時候,能看 API 文檔就看 API 文檔,切不可望文生義。

固然,Github 上最新的 JSON-Java 庫是沒有這個問題的。

小米開放平臺重磅推出小米賬號接入有禮活動:成功接入小米賬號便可得到小米開放平臺免費提供的平臺資源(小米應用商店、小米卡包、小米推送vip、小米賬號聯盟等資源),機會不容錯過,咱們期待您的加入!
活動報名地址:http://dev.xiaomi.com/console...

官方QQ交流羣:398616987
想要了解更多?
那就關注咱們吧!
圖片描述

相關文章
相關標籤/搜索