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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java中使用fastjson进行泛型类反序列化,你可以按照以下步骤进行操作: 首先,确保你已经引入了fastjson的依赖,可以通过Maven等构建工具添加以下依赖项: ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.79</version> </dependency> ``` 接下来,假设你有一个泛型类`Result<T>`,如下所示: ```java import com.alibaba.fastjson.JSON; public class Result<T> { private int code; private String message; private T data; public Result(int code, String message, T data) { this.code = code; this.message = message; this.data = data; } public int getCode() { return code; } public String getMessage() { return message; } public T getData() { return data; } public static <T> Result<T> fromJson(String json, Class<T> clazz) { return JSON.parseObject(json, new TypeReference<Result<T>>(clazz) {}.getType()); } } ``` 在上述代码中,`fromJSON`方法使用fastjson的`parseObject`方法将JSON字符串反序列化为`Result<T>`对象。 然后,你可以使用以下代码将JSON字符串反序列化为具体的泛型类对象: ```java import com.alibaba.fastjson.JSON; public class Main { public static void main(String[] args) { String json = "{\"code\":200,\"message\":\"Success\",\"data\":{\"name\":\"John\",\"age\":25}}"; Result<User> result = Result.fromJson(json, User.class); System.out.println("Code: " + result.getCode()); System.out.println("Message: " + result.getMessage()); User user = result.getData(); System.out.println("Name: " + user.getName()); System.out.println("Age: " + user.getAge()); } } ``` 在上述代码中,我们将一个包含`User`对象的JSON字符串反序列化为`Result<User>`对象,并输出其中的字段值。 请注意,为了正确反序列化泛型类,我们需要通过传递`Class<T>`参数给`fromJSON`方法来指定具体的泛型类型。 以上就是使用fastjson进行泛型类反序列化的基本示例。当然,根据实际需求和数据结构的复杂程度,你可能需要进行更多的定制和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值