聊聊fastjson反序列化的那些坑

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 聊聊fastjson反序列化的那些坑

1.gif场景


json作为一种可读性很强的格式,现在是广泛使用的。这里罗列两个我需要对json字符串进行反序列化的场景:


  1. 日志/数据库存储了接口的入参数据,希望通过反序列化来反查问题。
  2. 远程调用传过来的数据为json串,需要反序列化后使用。


而集团内使用得最多的就是FastJson了,这里分享几个我遇到的关于json反序列化的问题。


问题


 getter与setter类型不同


fastjson的序列化/反序列化是通过getter/setter方法来的,以如下User类为例,money字段的getter/setter类型不同:


public class User{
    public User(Long money, Date birthday){
      this.money = money;
        this.birthday = birthday;
    }
   private Long money;
    private Date birthday;
    public Money getMoney(){
      Money money = new Money();
        money.setMoney(this.money);
        money.setLocation("China");
        return money;
    }
    public void setMoney(long money){
      this.money = money;
    }
    public Date getBirthday(){
      return birthday;
    }
    public void setBirthDay(Date birthday){
      this.birthday = birthday;
    }
}
@Data
public class Money{
    private Long money;
    private String Location;
}

当我们用fastjson序列化User类的对象后,会得到如下字符串:


{
    "birthday":1639129158598,
    "money":{
        "location":"China",
        "money":10
    }
}

而反序列化,则会报错:


image.png



原因是setter需要的类型是Long,但无法用Long的解析器来解析Money格式的数据。


 没有无参构造函数


为了解决1中的问题,我将User对象反序列化为JSONObject,然后修改对应的字段为Long类型,然后进行反序列化。会发现,依然报错:


image.png


 Gson与FastJson对Date类的序列化/反序列化方式不同


为了解决2中的问题,决定将money字段改为Long类型后,转成字符串给Gson进行反序列化。原因是Gson的所有反序列化行为,都是基于反射做的,实际这也能避免fastjson存在的许多问题。然后发现,很不幸地,还是报错:


image.png

原因是Gson与FastJson对Date类的序列化/反序列化方式不同,fastjson会将Date序列化为时间戳。


 类中存在接口类


阅读fastjson代码com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#createInstance可以看到,对于接口类,它是新建的一个代理来做反序列化的:


image.png


这会存在一个问题,就是使用像hessian这种序列化/反序列化的远程调用时,这个代理对象在远端并无法被成功反序列化,从而导致远程调用异常。同时,一些基于子类类型做判断的逻辑也无法进行了。


解法


 getter与setter类型不同


可以将json字符串解析为JSONObject,然后再操作修改对应位置的值为其真实值,最后调用JSON.toJavaObject反序列化为对应类。


 没有无参构造函数


可以用Gson进行反序列化。Gson的所有反序列化行为,都是基于反射做的,实际这也能避免fastjson存在的许多问题。


 Gson与FastJson对Date类的序列化/反序列化方式不同


Gson可以添加对某种类的解析方法,这里使用如下方式添加Date类的定制解析方法,并创建Gson对象:


Gson gson = new GsonBuilder().registerTypeAdapter(
    Date.class, new TypeAdapter<Date>(){
        @Override
        public void write(JsonWriter out, Date value) throws IOException {
            if (value == null){
                out.nullValue();
            }else{
                out.value(value.getTime());
            }
        }
        @Override
        public Date read(JsonReader in) throws IOException {
            if (in != null){
                return new Date(in.nextLong());
            }else{
                return null;
            }
        }
    }).create();

 类中存在接口类


fastjson可以添加类的自定义反序列化方法:实现ObjectDeserializer接口,然后调用ParserConfig.getGlobalInstance().putDeserializer(Type, ObjectDeserializer)即可。
但是我们来看ObjectDeserializer接口需要实现的方法:

image.png


会发现,理解它并用它来反序列化自定义复杂类型,学习成本是比较高的。
通过阅读代码,可以看到,fastjson中对象的反序列化一般都是使用的com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer,它是继承ObjectDeserializer的,并且已经实现了几乎所有能力,所以实际上,我们继承它,然后重写其中的某些方法即可。这里我给出如下所示的重写方法并放进ParserConfig.getGlobalInstance()中:

ParserConfig.getGlobalInstance().putDeserializer(clazz, 
    new JavaBeanDeserializer(ParserConfig.getGlobalInstance(), clazz) {
    @Override
    public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName){
        //解析为JSONObject,方便业务使用并反序列化为对应的子类
        JSONObject jsonObj = this.deserialze(parser, JSONObject.class, fieldName, 0);
        //业务根据JSONObject创建对应子类对象逻辑
        T obj = ...
        return obj;
    }
    @Override
    public Object createInstance(Map<String, Object> map, ParserConfig config) //
        throws IllegalArgumentException {
        JSONObject jsonObj = null;
        if (map instanceof JSONObject){
            jsonObj = (JSONObject)map;
        }else if(map != null){
          jsonObj = new JSONObject(map);
        }
        //业务根据JSONObject创建对应子类对象逻辑
        T obj = ...
        return obj;
    }
});

这里重写了两个方法,第一个deserialze是ObjectDeserializer的接口方法,这里利用了JavaBeanDeserializer现有能力,将它转成JSONObject来给业务使用,学习成本会大大降低。


第二个方法是createInstance(Map map, ParserConfig  config),它的场景是,如果要反序列化的类不是接口类,而是有子类的父类(同样需要根据成员来判断应该反序列化为哪种具体的子类),并且此时是用JSON.toJavaObject反序列化JSONObject,那么如下图所示,它首先会调用createInstance方法(在com.alibaba.fastjson.util.TypeUtils#castToJavaBean(java.util.Map,  java.lang.Class,  com.alibaba.fastjson.parser.ParserConfig)中),因此我们也重写该方法。


image.png


写在最后


对于反序列化中的这些问题,大部分都能通过修改类成员模型及方法来解决,而其他域的数据,无法修改它的类模型,则只能用无侵入的方法来解决反序列化问题,对于一些复杂类,反序列化是困难重重的,但,办法总比问题多。


团队介绍


我们是淘系技术部营销工具团队,覆盖全域营销场景,拥有亿级用户规模、千万级商家规模。撒钱是我们的常规操作:百亿补贴、双11合伙人、央视春晚抽奖、全国消费券、这些具有影响力的营销产品可以让你既有业务scene,又有技术挑战。让商业营运高效而简单是我们的使命,懂数字商业、造技术引擎是我们的愿景。斗志昂扬?这里有广阔的平台。星辰大海,等待你来征服!



相关文章
|
JSON fastjson Java
Fastjson 序列化,反序列化Map对象排序问题(字符串转map,map转字符串)
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qingfeng812/article/details/61194594 ...
4875 0
|
15天前
|
存储 JSON fastjson
再也不用心惊胆战地使用FastJSON了——序列化篇
本篇将主要介绍json序列化的详细流程。本文阅读的FastJSON源码版本为2.0.31。
|
2月前
|
JSON fastjson Java
niubility!即使JavaBean没有默认无参构造器,fastjson也可以反序列化。- - - - 阿里Fastjson反序列化源码分析
本文详细分析了 Fastjson 反序列化对象的源码(版本 fastjson-1.2.60),揭示了即使 JavaBean 沲有默认无参构造器,Fastjson 仍能正常反序列化的技术内幕。文章通过案例展示了 Fastjson 在不同构造器情况下的行为,并深入探讨了 `ParserConfig#getDeserializer` 方法的核心逻辑。此外,还介绍了 ASM 字节码技术的应用及其在反序列化过程中的角色。
75 10
|
JSON 前端开发 fastjson
fastjson全局序列化坑
fastjson全局序列化坑
94 0
|
前端开发 fastjson
mvc配置fastjson序列化枚举
mvc配置fastjson序列化枚举
120 0
|
缓存 NoSQL Java
最详细 | redis实战:JackSon/FastJson方式序列化深度解析
Jackson2JsonRedisSerializer和FastJsonRedisSerializer的实战应用
3282 0
|
fastjson Java
fastjson全局日期序列化设置导致JSONField无效
fastjson通过代码指定全局序列化返回时间格式,导致使用JSONField注解标注属性的特殊日期返回格式失效
225 0
|
JSON 缓存 安全
fastjson反序列化历史与检测
fastjson反序列化历史与检测
|
Java fastjson
fastJson序列化与反序列化
fastJson序列化与反序列化
|
JSON fastjson Java
Fastjson反序列化随机性失败
Fastjson作为一款高性能的JSON序列化框架,使用场景众多,不过也存在一些潜在的bug和不足。本文主要讲述了一个具有"随机性"的反序列化错误!
325 0
Fastjson反序列化随机性失败