JSONObject为Android系统中的org.json.JSONObject。
问题发现
review代码时,看到一个提交记录,修复了JSONObject optLong丢失精度的问题。
更改前为
long id = jsonObject.optLong(idKey)
更改后为
long id = optLong(jsonObject, idKey)
private long optLong(JSONObject jsonObject, String key) {
try {
return Long.valueOf(jsonObject.optString(key));
} catch (NumberFormatException e) {
e.printStackTrace();
}
return jsonObject.optLong(key);
}
解析的是服务端下发的json数据,为何JSONObject.optLong()会丢失精度呢?
Demo验证
写个demo测了下
val jsonObj = JSONObject()
jsonObj.put("long_str", (Long.MAX_VALUE -1).toString()) // 注意这里!!!
Log.e("sss", "$jsonObj")
val doubleValue = jsonObj.optDouble("long_str")
val longValue = jsonObj.optLong("long_str")
val longFromString = getLongFromStr(jsonObj)
Log.e("sss", "$longValue $longFromString ${longValue == longFromString} $doubleValue")
其中,getLongFromStr 为
private fun getLongFromStr(jsonObj: JSONObject): Long {
try {
return jsonObj.optString("long_str").toLong()
} catch (e: NumberFormatException) {
e.printStackTrace()
}
return jsonObj.optLong("long_str")
}
看下输出结果:
E/sss: {"long_str":"9223372036854775806"}
E/sss: 9223372036854775807 9223372036854775806 false 9.223372036854776E18
!!!! 通过optString方式取的值是对的,通过optLong方式取的值却是错的!!!
但如果简单改下demo
val jsonObj = JSONObject()
jsonObj.put("long_str", Long.MAX_VALUE -1) // 改动点
Log.e("sss", "$jsonObj")
val doubleValue = jsonObj.optDouble("long_str")
val longValue = jsonObj.optLong("long_str")
val longFromString = getLongFromStr(jsonObj)
Log.e("sss", "$longValue $longFromString ${longValue == longFromString} $doubleValue")
输出结果却是:
E/sss: {"long_str":9223372036854775806}
E/sss: 9223372036854775806 9223372036854775806 true 9.223372036854776E18
!!!optString()和optLong()两种方法取的值都是对的!!!
分析
看下JSONObject的optLong()方法
public long optLong(@Nullable String name, long fallback) {
Object object = opt(name);
Long result = JSON.toLong(object);
return result != null ? result : fallback;
}
内部是通过JSON.toLong()方法实现的
static Long toLong(Object value) {
if (value instanceof Long) {
return (Long) value;
} else if (value instanceof Number) {
return ((Number) value).longValue();
} else if (value instanceof String) {
try {
return (long) Double.parseDouble((String) value);
} catch (NumberFormatException ignored) {
}
}
return null;
}
如果Object为Long的话,直接走第一个if分支,不存在精度损失;
如果Object为String的话,走第三个if分支,先将String类型的数解析成双精度浮点型,再转成Long型,这就可能造成精度丢失,具体原因可参考float,double等精度丢失问题
经验
在做JSON构造和解析时,应确定好数据格式,避免出现数据丢失的情况。对于客户端而言,JSON解析时,数据类型可以统一先转成String,再做String到相应数据类型的转换处理。