当前位置: 首页 > news >正文

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

抛出问题

因为目前项目是以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}"
  }
}复制代码

可以看出来

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

因为org.json是系统库,于是很容易就想到是sdk版本实现差异导致的。

寻找原因

  • 从代码和结果上分析

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

对应到代码上

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) 这个构造方法。


转载于:https://juejin.im/post/5b88ed0f6fb9a019cf2cca6e

相关文章:

  • 封装html代码块到js函数中
  • K8S集群tls证书管理
  • Android -- DragDrop
  • 一个完整Java Web项目背后的密码
  • PHP字符串操作(string替换、删除、截取、复制、连接、比较、查找、包含、大小写转换、切割成数组等)...
  • vue element ui excel json2csv csv 导出
  • 这7个不可错过的数据可视化技术,让你的位置信息跃然纸上
  • JAVA运维-Tomcat支持APR模式
  • 路由器密码忘了怎么办 自己动手不求人
  • 源码编译安装 PHP 7.1.5 + nginx 1.12.0
  • 微信分享JS-SDK
  • 独家 | 环境大数据的应用案例及前景
  • P4165 [SCOI2007]组队
  • 跨域问题
  • laraval+node.js实现websocket
  • 【个人向】《HTTP图解》阅后小结
  • JavaScript的使用你知道几种?(上)
  • JavaScript类型识别
  • leetcode-27. Remove Element
  • MYSQL 的 IF 函数
  • Mysql数据库的条件查询语句
  • Mysql优化
  • Node 版本管理
  • SpringBoot 实战 (三) | 配置文件详解
  • SQLServer之创建数据库快照
  • Unix命令
  • uva 10370 Above Average
  • vue 个人积累(使用工具,组件)
  • web标准化(下)
  • 记一次删除Git记录中的大文件的过程
  • 算法-插入排序
  • 自动记录MySQL慢查询快照脚本
  • ​批处理文件中的errorlevel用法
  • ​油烟净化器电源安全,保障健康餐饮生活
  • #1015 : KMP算法
  • #etcd#安装时出错
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • $.proxy和$.extend
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (切换多语言)vantUI+vue-i18n进行国际化配置及新增没有的语言包
  • (十八)用JAVA编写MP3解码器——迷你播放器
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (转)Mysql的优化设置
  • (转)视频码率,帧率和分辨率的联系与区别
  • ***监测系统的构建(chkrootkit )
  • .equals()到底是什么意思?
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .Net CF下精确的计时器
  • .NET CLR Hosting 简介