使用JSONObject时,你需要注意避免的一个问题

移动开发 Android 开发工具
在 Android 业务同步的逻辑代码中,使用到了 JSONObject 来解析服务端的 JSON 数据。同时本地因为业务新增需求的缘故,在本地数据库中使用 JSONObject 缓存了包括水位等同步相关的信息,其中,水位值是 Long 型。

[[177050]]

鲍协浩,小米MIUI部门, MIUI基础应用组通讯录开发负责人。

 

问题现象

在 Android 业务同步的逻辑代码中,使用到了 JSONObject 来解析服务端的 JSON 数据。同时本地因为业务新增需求的缘故,在本地数据库中使用 JSONObject 缓存了包括水位等同步相关的信息,其中,水位值是 Long 型。但近期发现同步过程中下一次同步时,传递给服务器的水位并不是上一次服务器返回的新水位,而是相差一些。以 301028292893495297L 为例,服务器返回这个水位之后,下次客户端上传的水位是 301028292893495296L,差值为 -1。

问题排查

通过反复排查代码逻辑,发现水位从服务端返回到下次请求之间,只经过了以下转换:

认真阅读代码不难发现,Long 型的水位值保存在 JSON 对象中的时候被转成了 String 型,而在读取的时候又当作是 Long 型来处理。所以就会有精度缺失的问题吗?马上参考 JSONObject 的文档:

 

 

 

 

认真阅读代码不难发现,Long 型的水位值保存在 JSON 对象中的时候转成了 String 型,而在读取的时候又当作是 Long 型来处理。因此会有精度缺失的问题,参见如下 JSONObject 的文档:

由此可见,在读取 JSON 对象的某个值时,如果原先是 String 型,读取的时候当作是 Long 型,是会将 String 型通过 Double 进行解析的,所以在值超过 2^52 时会有精度缺失的问题。于是,遇到的问题就可以解释了。以下是 Double 的存储格式规范:

 

 

 

 

其中,Double 和 Long 的精度测试代码很简单(输入参数可以提供例如 301028292893495297L 这样超过 2^52 的 long 值,会发现其返回值不为 0):

 

 

 

 

也就是说,在读取 JSON 对象的某个值时,如果原先是 String 型,读取的时候当作是 Long 型,是会将 String 型通过 Double 进行解析的,所以在值超过 2^52 时会有精度缺失的问题。

另外,关于 JSON 对象中的值是 Long 型还是 String 型,其实比较容易被忽略。如果JSON 对象在使用 String 表示的时候,该值对应处有引号就是 String 型,否则就是其他类型。看如下的 2 个测试用例就一目了然:

Double 和 Long 的精度测试代码很简单(输入参数可以提供例如 301028292893495297L 这样超过 2^52 的 long 值):

知道了问题的根源,修复就一目了然了,在水位保存在 JSONObject 对象中时,应该当作 Long 型而不是 String 型来保存;亦或者在读取的时候也当作是 String 型,然后通过 Long.valueOf 等接口进行解析。

另外,关于 JSON 对象中的值是 Long 型还是 String 型,其实比较容易被忽略。如果JSON 对象在使用 String 表示的时候,该值对应处有引号就是 String 型。看如下的试用例就一目了然了:

类似的问题在网上随意一搜,其实有许多人遇坑了,比如这个。

 

 

 

 

所以,尽管不能说这个库的设计是很失败的,但肯定不算是一个设计良好的库。因为你无法直接从 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 库是没有这个问题的。

【本文是51CTO专栏“小米开放平台”的原创文章,“小米开放平台”微信公众号:xiaomideveloper】

 

责任编辑:庞桂玉 来源: 小米开放平台
相关推荐

2016-12-26 18:51:34

AndroidJavascriptJSONObject

2012-07-04 14:40:37

Ajax

2024-05-16 15:15:14

2011-07-26 09:19:27

Objective-C 重载

2023-10-04 00:03:00

SQL数据库

2010-12-31 09:14:36

MongoDB

2021-02-24 07:40:38

React Hooks闭包

2015-10-08 10:07:29

游戏开发内存使用

2010-06-29 15:54:36

UML建模

2011-12-21 09:54:15

项目经理

2021-02-05 17:35:07

数据高管CIO技术

2009-04-23 14:30:19

UML建模

2013-09-29 10:36:08

VMware虚拟化

2020-10-26 14:01:22

Java泛型

2010-08-09 11:23:24

Flex开发

2010-10-08 09:38:42

mysql修改表

2021-12-30 06:59:28

方法重写面试

2014-01-26 14:24:25

开源项目

2016-09-23 16:09:01

2017-03-17 11:00:08

数字化陈勇Gartner
点赞
收藏

51CTO技术栈公众号