Java泛型与Json反序列化

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的自带类型,而TypeToken是Gson额外引入的,这种设计的效果是Gson的接口非常简单。Fastjson的接口可以支持Type参数,也支持TypeReference参数。

Gson和Fastjson的对外方法对比

说了这么多,下面两张图,对比一下Fastjson对外同的公共方法数量和Gson提供的公共方法数量,有比较直观的感觉:


Gson对外接口-数量明显更少

FastJson对外接口-数量明显非常多

小结

整体上能明显看出来fastjson更多是长出来的,接口多而全,应该是不断有人提需求不断实现的结果,而Gson是设计出来的,接口的一致性很强,高内聚低耦合,有些时候宁愿牺牲接口的便利性,也要保证接口对外的一致性、简单和概念完整,从设计上我是崇尚Gson的设计理念的,但实际的开发过程更容易演变成fastjson的模式,在中国程序员的地位真的不够高。

参考资料

[1]. fastjson源代码
[2]. Gson源代码
[3]. https://github.com/google/gson/blob/master/GsonDesignDocument.md

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • 1.概述2.Gson的目标3.Gson的性能和扩展性4.Gson的使用者5.如何使用Gson 通过Maven来使用...
    人失格阅读 14,228评论 2 18
  • 为了更好的学习Gson,特将Gson User Guide翻译如下。由于本人英文水平有限,如有错误,还请指正,谢谢...
    WeberLisper阅读 6,796评论 0 6
  • 概况 Gson是一个Java库,它可以用来把Java对象转换为JSON表达式,也可以反过来把JSON字符串转换成与...
    木豚阅读 6,786评论 0 2
  • ​​工程机械 中国中铁和中信重工走势对比: a、 由于工程机械(2014年6月底)见底滞后于铁路基建(2014年3...
    逍遥魏紫阅读 269评论 0 0
  • 为什么辛辛苦苦收拾干净的小家,保持不了几天就又乱掉呢?为什么添置了一些收纳神器,还是收拾不完繁杂的小物呢?打理家...
    淮水东边阅读 1,196评论 0 51