作者:包协豪,小米MIUI部,MIUI基础应用组通讯录开发总监问题现象在Android业务同步的逻辑代码中,使用JSONObject解析JSON数据在服务器上。同时,由于业务的新需求,在本地数据库中使用JSONObject来缓存包括水位在内的同步相关信息,其中水位值是Long类型。但是最近发现在同步过程中下次同步时,传递给服务器的水位并不是上次服务器返回的新水位,而是有点不同。以301028292893495297L为例,服务端返回这个水位后,客户端下次上传的水位为301028292893495296L,差值为-1。问题排查通过反复检查代码逻辑,发现水位从服务端返回到下一次请求只进行了如下转换:仔细阅读代码不难发现,水位值是Long类型在存储到JSON对象Type中时会被转换成String,读取时会被当作Long类型处理。因此,就会出现精度不够的问题。请参考下面的JSONObject文档:可以看出,在读取一个JSON对象的值时,如果是String类型,读取时会认为是Long类型,读取的是String类型。它是用Double解析的,所以当值超过2^52时会出现精度不足的问题。这样,遇到的问题就可以解释了。下面是Double的存储格式说明:其中Double和Long的精度测试代码很简单(输入参数可以提供超过2^52的long值比如301028292893495297L,你会发现返回值为not0):Double和Long的精度测试代码很简单(入参可以提供超过2^52的long值如301028292893495297L):知道问题根源,修复一目了然.当水位存储在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类型;.valueOf和其他用于解析的接口。题完后上网搜了下类似的题。其实很多人都遇到过坑,比如这个。因此,虽然不能说这个库的设计是失败的,但它肯定不是一个设计良好的库。因为不能直接从API名称看出底层逻辑,导致使用不当。所以,吸取的教训是:在使用第三方库时,只要能看API文档就看API文档,千万不要照字面意思。当然Github上最新的JSON-Java库没有这个问题。
