从源码分析JSONObject因版本差别致使toString格式异常问题

抛出问题

由于目前项目是以TCP通讯为主,使用自研协议解析工具来解析自定义的传输协议。json

因此并无引入第三方JSON解析库,目前仍是依赖原生JSON解析库进行解析。bash

使用如下代码构建一个JSONObject,并将其toString。app

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协议标准的stringide

{
  "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}"
  }
}复制代码

能够看出来工具

`key=data`对应的`value`值变成一个字符串了,原本应该被解析成为一个JSONObject的。

由于org.json是系统库,因而很容易就想到是sdk版本实现差别致使的。源码分析

寻找缘由

  • 从代码和结果上分析ui

在有问题的手机上获得的结果中能够看出来,一直到第二层的JSONObject,都是能够正常解析的。this

对应到代码上spa

jo.put("result", new JSONObject(joParams)); 复制代码

能够看到,

`result`对应的`value`是一个明确的JSONObject,因此toString时,将其解析为JSON string,这没毛病。

但为何第三层

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时有两种状况:

  • 若是是JSONArrayJSONObject,会递归调用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进行了处理。对CollectionarrayMap 这几种集合容器作了处理,使用明确的JSONObjectJSONArray来代替它们。而sdk 4.2.2中并无作这样的处理。

在以前分析的JSONObject#toString工做原理的基础上,再回到最开始的那段代码中,由于第三层的key-value集合是一个Map,因此,将会调用Map#toString方法来生成value。

这就是为何那段代码会在不一样版本的平台上表现出差别的缘由啦~

解决方案

  1. 直接在上层代码作修改,使用明确的JSONObject代替CollectionarrayMap 这几种集合容器。

  2. 模仿sdk 8.0的处理方式,重载JSONObject(Map params) 这个构造方法。

相关文章
相关标签/搜索