抛出问题
因为目前项目是以TCP通信为主,使用自研协议解析工具来解析自定义的传输协议。
所以并没有引入第三方JSON解析库,目前还是依赖原生JSON解析库进行解析。
使用以下代码构建一个JSONObject,并将其toString。
public static String toJson(JsEvent jsEvent) {
JSONObject jo = new JSONObject();
try {
jo.put(TYPE, jsEvent.getType());
jo.put(IDENTIFIER, jsEvent.getIdentifier());
String method = jsEvent.getMethod();
jo.put(METHOD, method);
Map<String,Object> joParams = new HashMap<>();
joParams.put("code", code);
joParams.put("msg", msg);
Map<String, Object> data = new HashMap<>();
data.put("token", "authToken");
joParams.put("data", data);
jo.put("result", new JSONObject(joParams));
} catch (Exception e) {
....
}
return jo.toString();
}复制代码
以上代码可以在绝大部分设备上得到符合JSON协议标准的string
{
"type": "Callback",
"identifier": "1",
"method": "getToken",
"result": {
"msg": "",
"code": 0,
"data": {
"token": "tgmj2o8rs9n4s24psq18bu6k6y0tycpf"
}
}
}复制代码
但在sdk <= Android 4.3的手机上,却得到以下JSON string
{
"type": "Callback",
"identifier": "1",
"method": "getToken",
"result": {
"msg": "",
"code": 0,
"data": "{token=tgmj2o8rs9n4s24psq18bu6k6y0tycpf}"
}
}复制代码
可以看出来
因为org.json是系统库,于是很容易就想到是sdk版本实现差异导致的。
寻找原因
从代码和结果上分析
在有问题的手机上得到的结果中可以看出来,一直到第二层的JSONObject,都是可以正常解析的。
对应到代码上
jo.put("result", new JSONObject(joParams)); 复制代码
可以看到,
但为什么第三层
Map<String, Object> data = new HashMap<>();
data.put("token", "authToken");
joParams.put("data", data);复制代码
使用这样的方式去构建JSONObject,在Android 4.3以上可以正常解析,但4.3以下却不行呢?
经过以上的分析,我们可以大概把焦点锁定在JSONObject(Map params)
这个构造方法中 和 JSONObject#toString
这两个方法中。
从JSONObject源码分析
简单阐述一下JSONObject#toString
的工作原理:
调用writeTo
方法去遍历JSONObject
中的nameValuePairs
,将key->value
处理成字符串的形式。在处理value
时有两种情况:
如果是
JSONArray
或JSONObject
,会递归调用writeTo
方法,继续进行处理。
如果非以上两个特殊类型,都会将其转换成string(调用
Object#toString
或者其他方法),并且append到结果中。
然后再来看一下表现正常的 sdk 8.0 的JSONObject(Map params)
方法实现。
public JSONObject(Map copyFrom) {
this();
Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom;
for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
String key = (String) entry.getKey();
if (key == null) {
throw new NullPointerException("key == null");
}
nameValuePairs.put(key, wrap(entry.getValue()));
}
}
复制代码
表现异常的sdk 4.2.2的JSONObject(Map params)
方法实现。
public JSONObject(Map copyFrom) {
this();
Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom;
for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
String key = (String) entry.getKey();
if (key == null) {
throw new NullPointerException("key == null");
}
nameValuePairs.put(key, entry.getValue());
}
}复制代码
可以很明显的看出来,sdk 8.0在遍历map时,调用wrap函数对value进行了处理。对Collection
、array
、Map
这几种集合容器做了处理,使用明确的JSONObject
或JSONArray
来代替它们。而sdk 4.2.2中并没有做这样的处理。
在之前分析的JSONObject#toString
工作原理的基础上,再回到最开始的那段代码中,因为第三层的key-value集合是一个Map,所以,将会调用Map#toString
方法来生成value。
这就是为什么那段代码会在不同版本的平台上表现出差异的原因啦~
解决方案
直接在上层代码做修改,使用明确的JSONObject代替
Collection
、array
、Map
这几种集合容器。模仿sdk 8.0的处理方式,重载
JSONObject(Map params)
这个构造方法。