Json反序列化与Java泛型
Java的JSON库有很多,本文分析google的Gson和alibaba的fastjson,在Java泛型场景反序列化的一些有意思的行为。考虑下面的json字符串:
[ "2147483648", "2147483647" ]
用fastjson在不指定类型的情况下解析,下面的代码输出啥:
JSON.parseArray(s).forEach(o -> { System.out.println(o.getClass()); });
答案是:
class java.lang.Long class java.lang.Integer
是不是感觉有点儿奇怪,两个都是数字啊,居然输出了不同的类型,原因我们下面细讲。再看看Gson, 用Gson解析并且不指定泛型类型的话,下面的代码输出啥:
new Gson().fromJson(s, List.class).forEach(o -> { System.out.println(o.getClass()); });
答案是:
class java.lang.Double class java.lang.Double
这次两个都是Double类型,明明是整数啊,为啥到Gson这里变成Double了?
我们试了两个Json库,解析相同的json字符串,得到全然不同的结果。如果在实际使用的时候,用这种方式反序列化JSON,很容易出现BUG,而且是运行时才可能出现,这种问题一旦出现往往很难排查。因此为了保证泛型类型Json反序列化的正确性,一定要明确指定泛型的类型。下面我们先看看正确的解析应该怎么写,再深度探讨一下内部的原理。
正确的泛型Json反序列化
fastjson和Gson都提供了泛型类型的反序列化方案,先来看看fastjson,对于上面的case,正确的反序列化代码如下:
JSON.parseObject(s, new TypeReference<List<Long>>(){}) .forEach(o -> { System.out.println(o.getClass()); });
创建一个确定泛型类型的TypeReference
子类(这里是匿名内部类),将这个子类传递给fastjson以帮助fastjson在运行时获得泛型的具体类型信息,从而实现泛型正确反序列化。Gson的方案与fastjson相同,或者应该反过来说fastjson的方案与Gson相同。大家感兴趣的话可以看看fastjson的TypeReference
和Gson的TypeToken
代码,基本上fastjson就是抄袭Gson,连注释都抄了......。Gson指定泛型类型的反序列化方法如下,也是创建一个确定泛型类型的匿名子类:
new Gson().<List<Long>>fromJson(s, new TypeToken<List<Long>>(){}.getType()) .forEach(o -> { System.out.println(o.getClass()); });
由于fastjson的方案是来自Gson,以下只讨论Gson的泛型反序列化原理。为什么泛型的反序列化显得这么麻烦呢,非要通过子类化的方式,不能直接告诉Gson泛型的类型吗?是的,Java是真的做不到,其实理由很简单,泛型类既然是泛型,意味着就不应该带有具体泛型类型的信息,因此泛型类的字节码本身就不应该也无法保存泛型类型,但是子类如果继承一个明确泛型类型的父类(父类是一个泛型类型,Gson里面就是TypeToken),子类必须保存父类的明确类型信息,通过Class
类的getGenericSuperclass
方法能够获得父类类型信息,该类型信息包含了父类具体的泛型类型信息。这个方法帮助Java的泛型体系完整化了,是非常重要的一个方法。
类库设计分析
面对相同的设计问题,fastjson与Gson在很多点上的选择不同,借此机会可以一窥类库设计的思想,让我们一一来看。
是静态方法还是实例化
使用Gson之前,必须进行实例化,Gson提供了两种方式:一种是无参数构造器,一种是通过GsonBuilder,后者能够进行更多的定制,但无论是哪种方法,都需要实例化一个Gson对象。但是Fastjson使用之前是不需要实例化的,直接使用JSON类的静态方法即可实现json序列化和反序列化。这一点上来讲,Fastjson比较方便,虽然Gson是线程安全的,可以用static变量来声明一个Gson实例(饿汉模式的单例)然后全局使用,但是还是比Fastjson多了一步。但是Gson这么做的好处是如果序列化(反序列化)的定制比较多,可以在初始化的时候完成复杂的扩展定制,使用的时候依然保持简单,Fastjson就需要每次都传递额外的参数来实现。总体来讲各有优化,Gson是线程安全的,大部分场景都是定义全局的静态单例,用起来跟Fastjson差不多。Gson在这里的选择倾向于希望对外的接口保持一致和简单,即无论怎么定制逻辑,Json的序列化和反序列化就那么几个方法,内部逻辑可以定制,但是使用接口不用改变。
数字的默认类型
对于数字类型,如果没有明确指定类型,Gson默认都解析成Double类型,而Fastjson会根据数字的不同,解析成Long、Integer或者BigDecimal。我们在生产中用Fastjson就遇到这种问题:由于集合没有指定泛型类型,反序列化的时候,不同大小的数字被反序列化成了不同的类型,导致业务逻辑出错。这种未制定类型情况下,感觉Gson的处理更合适一些,既然未指定类型,对外的默认类型始终是Double,接口对外的心智更稳定。
集合类型反序列化
对于列表类型的反序列化,Fastjson提供了parseArray系列方法,这样很多情况下可以避免使用TypeReference
,代码写起来更简单。但是Gson就没有这种方法,如果需要解析列表,必须使用TypeToken<List<Xxx>>
,并没有为列表设置特殊的方法,这里依然能看到 Gson希望对外的接口保持一致和简单 ,即便牺牲一点儿方便性。
泛型反序列化
为了解析泛型,Gson和Fastjson都提供了类似的机制(Gson使用TypeToken承载类型,而Fastjson使用TypeReference承载类型),利用子类继承确定泛型类型父类的方式,获得类型,区别是Gson的接口只接受Type类型的参数,不接受TypeToken参数,这是因为Type是JDK的自带类型,这种设计的效果是Gson的接口非常简单。Fastjson的接口可以支持Type参数,也支持TypeReference参数。
小结
整体上能明显看出来fastjson更多是长出来的,接口多而全,应该是不断有人提需求的结果,而Gson是设计出来的,接口的一致性很强,高内聚低耦合,有些时候宁愿牺牲接口的便利性,也要保证接口对外的一致性、简单和概念完整,从设计上我是崇尚Gson的设计理念的,但实际的开发过程更容易演变成fastjson的模式,在中国程序员的地位真的不够高。
参考资料
[1]. fastjson源代码
[2]. Gson源代码
[3]. https://github.com/google/gson/blob/master/GsonDesignDocument.md
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
淘宝api Python 接口升级 3.0 版本
因为自学 python 工作中会经常用到淘宝Api调用数据 一直以来后台下载的淘宝Api 都是2.7版本 还是12年 lihao同学编写,一直没有升级 用Python 自带的2to3脚本工具升级后 大部分接口 调用正常 但是上传图片接口 一直提示错误 由于是初学 只能网上找资料了 找了很多资料 都没解决 最后通过国外友人的一篇博客 找到了解决方法 在这里分享一下 希望能帮助其他人 只要替换 base.py 代码就可以 初步测试调用接口 都能成功 # -*- coding: utf-8 -*- """ Created on 2012-7-3 @author: lihao """ try: import httplib except ImportError: import http.client as httplib import urllib import time import hashlib import json import io import top import sys import itertools import mimetypes from urllib.parse i...
- 下一篇
如何高效地遍历 MongoDB 超大集合?
GitHub 仓库:Fundebug/loop-mongodb-big-collection 本文使用的编程语言是 Node.js,连接 MongoDB 的模块用的是mongoose。但是,本文介绍的方法适用于其他编程语言及其对应的 MongoDB 模块。 错误方法:find() 也许,在遍历 MongoDB 集合时,我们会这样写: const Promise = require("bluebird"); function findAllMembers() { return Member.find(); } async function test() { const members = await findAllMembers(); let N = 0; await Promise.mapSeries(members, member => { N++; console.log(`name of the ${N}th member: ${member.name}`); }); console.log(`loop all ${N} members success`); } test(...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS6,CentOS7官方镜像安装Oracle11G
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS关闭SELinux安全模块
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果