FastJson踩坑:@JsonField在反序列化时失效的解决

问题描述

一个对象(某个字段为枚举类型,为了不采用默认的序列化过程,用@JSONField指定了序列化器和反序列器,过程见旧博文),将其放到JSONArray中再序列化JSONArray对象,用得到的JSON字符串再反序列化时,发现能够正常反序列化出JSONArray,而对JSONArray中的某个元素再反序列化成类对象时,出错。

示例

同样用旧博文的示例做个简单测试。

基本对象类Article。

public class Article {
    private String title;
    private String content;
    @JSONField(serializeUsing = AuditStatusCodec.class, deserializeUsing = AuditStatusCodec.class)
    private AuditStatus status;
    public Article(){
    }
    public Article(String title, String content, AuditStatus status){
        this.title = title;
        this.content = content;
        this.status = status;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public AuditStatus getStatus() {
        return status;
    }
    public void setStatus(AuditStatus status) {
        this.status = status;
    }
    @Override
    public String toString() {
        return "Article{" +
                "title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", status=" + status +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o){
            return true;
        }
        if (o == null || getClass() != o.getClass()){
            return false;
        }
        Article article = (Article) o;
        return Objects.equals(title, article.title) &&
                Objects.equals(content, article.content) &&
                status == article.status;
    }
    @Override
    public int hashCode() {
        return Objects.hash(title, content, status);
    }
}

枚举类型AuditStatus。

public enum AuditStatus {
    /**
     * 审核中
     */
    AUDITING(1),
    /**
     * 通过
     */
    PASSED(2),
    /**
     * 失败
     */
    FAILED(3);
    private int code;
    AuditStatus(int code){
        this.code = code;
    }
    public int getCode() {
        return code;
    }
    public static AuditStatus convert(int code){
        AuditStatus[] enums = AuditStatus.values();
        for(AuditStatus e : enums){
            if(e.code == code){
                return e;
            }
        }
        return null;
    }
}

以及序列化/反序列化器AuditStatusCodec

public class AuditStatusCodec implements ObjectSerializer, ObjectDeserializer {
    @Override
    public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
        Object value = parser.parse();
        return value == null ? (T) value : (T) AuditStatus.convert(TypeUtils.castToInt(value));
    }
    @Override
    public int getFastMatchToken() {
        return 0;
    }
    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
        serializer.write(((AuditStatus)object).getCode());
    }
}

按照出问题的情况,写模拟用例:

public class FastJsonTest {
    @Test
    public void deserializeTest(){
        testJSONParse();
        testJSONArrayParse();
    }
    protected static void testJSONParse(){
        System.out.println("**************Start Test JSON Parse");
        Article originalArticle = new Article("Article 1", "This is content", AuditStatus.AUDITING);
        String jsonStr = JSON.toJSONString(originalArticle);
        System.out.println("Serialize to json string: " + jsonStr);
        Article deserializeArticle = JSON.parseObject(jsonStr, Article.class);
        System.out.println("Deserialize to Java Object: " + deserializeArticle + "; and the status is " + deserializeArticle.getStatus());
        //Equals
        Assert.assertTrue(deserializeArticle.getStatus().equals(AuditStatus.AUDITING));
        Assert.assertEquals(originalArticle, deserializeArticle);
    }
    protected static void testJSONArrayParse(){
        System.out.println("**************Start Test JSONArray Parse");
        JSONArray arr = new JSONArray();
        Article originArticle = new Article("Article 1", "This is content", AuditStatus.AUDITING);
        arr.add(originArticle);
        String jsonArrStr = JSON.toJSONString(arr);
        System.out.println("Serialize to json array string: " + jsonArrStr);
        arr = JSON.parseArray(jsonArrStr);
        Article deserializeArticle = arr.getObject(0, Article.class);
        System.out.println("Deserialize to json arr, then to java object: " + deserializeArticle + "; ant the status is " + deserializeArticle.getStatus());
        //Not Equals
        Assert.assertFalse(deserializeArticle.getStatus().equals(AuditStatus.AUDITING));
        Assert.assertNotEquals(originArticle, deserializeArticle);
    }
}

看控制台输出的情况:

**************Start Test JSON Parse
Serialize to json string: {"content":"This is content","status":1,"title":"Article 1"}
Deserialize to Java Object: Article{title='Article 1', content='This is content', status=AUDITING}; and the status is AUDITING
**************Start Test JSONArray Parse
Serialize to json array string: [{"content":"This is content","status":1,"title":"Article 1"}]
Deserialize to json arr, then to java object: Article{title='Article 1', content='This is content', status=PASSED}; and the status is PASSED

上述代码中testJsonParse没有把类对象放到JSONArray中,可以从结果中看出序列化和反序列化过程均正常。

而testJSONArrayParse先把类对象放到JSONArray中,在从JSONArray中取出对象反序列化,反序列化的结果就不正常了。

疑问

为什么JSONObject和JSONArray的反序列化过程得到的结果不一致?两者的反序列过程差异在哪?

DEBUG

遇事不决,开始DEBUG。

JSON.parseObject的流程

首先,JSON是一个门面类,提供出一些静态的方法供外部使用。比如说parseObject()方法。其内部会创建解析器DefaultJSONParser,并将解析委托给解析器执行。

DefualtJSONParser在创建时接受输入,全局配置及特性,相当于获取到了本次解析所有的数据。同时DefualtJSONParser的内部创建了一些用于解析的组件,例如JSONLexer(用于字符串解析)。解析过程在parseObject中执行,parseObject会通过ParseConfig(保存解析配置的一个全局对象)获取到解析器ObjectDeserializer,并由解析器处理真正的解析过程。

在通过Class获取ObjectDeserializer时,首先会确定ParserConfig中是否缓存了对应的反序列化器,如果不存在,则会新建一个JavaBeanDeserializer(对于一般Java对象而言)。在新建过程中,会解析Class的属性,并保存在JavaBeanInfo中。 解析器的解析过程就是对比JSON字符串中的KEY和JavaBeanInfo的过程,把对应的值反序列化出来(判断是否有JSONField注解,并根据注解的属性处理也在这一步),最终还原对象。

以流程图表示上述过程:

JSONArray.getObject()

JSONArray.getObject()会先从JSONArray中获取出Object,然后调用TypeUtils对Object通过TypeUtils.castToJavaBean()转型。

TypeUtils通过根据需要转型的类型从ParserConfig中获取ObjectDeserializer反序列化器,对于普通 Java Bean 而言,是JavaBeanDeserializer。

由于JSONArray中取出的Object实际上是JSONObject对象,因此会由JavaBeanDeserializer反序列化器的createInstance()方法执行反序列化,得到对象。

以流程图表示上述过程:

deserialize 和 createInstance 的不同

deserialize在反序列化时,会从class上获取更多的属性,其中就包括JSONField注解上的信息,而createInstance获取的信息较少,因此忽略JSONField所带的信息,导致自定义的反序列化器在反序列化时失效。

疑惑

为什么都是反序列化过程,二者在行为和表现上会有所不同?官方是如何定义deserialize和createInstance的?

上述这些问题还需要查询更多资料来明确。也希望了解缘由的读者进行告知。

问题的解决方式

解决的办法不先转换成JSONArray,然后再反序列化对象。而是通过JSON.parseArray直接转成对象的List。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • java中fastjson生成和解析json数据(序列化和反序列化数据)

    本文讲解2点: 1. fastjson生成和解析json数据 (举例:4种常用类型:JavaBean,List<JavaBean>,List<String>,List<Map<String,Object>) 2.通过一个android程序测试fastjson的用法. fastjson简介: Fastjson是一个Java语言编写的高性能功能完善的JSON库.fastjson采用独创的算法,将parse的速度提升到极致,超过所有json库,包括曾经号称最快的jack

  • springmvc fastjson 反序列化时间格式化方法(推荐)

    第一种情况是从后台拿到数据,进行反序列化,反序列化格式时间:试了一下很多网上的方法,最后发现还是在实体类上面的日期字段加上如下注解,可以完成格式化操作,否则默认就都是时间戳的格式: @JSONField (format="yyyy-MM-dd HH:mm:ss")  public Date birthday; @JSONField (format="yyyy-MM-dd HH:mm:ss")  public Date birthday; 第二种情况是:respons

  • 关于fastjson的@JSONField注解的一些问题(详解)

    @JSONField 看源码它可以作用于字段和方法上. 引用网上说的, 一.作用Field @JSONField作用在Field时,其name不仅定义了输入key的名称,同时也定义了输出的名称. 但是我在使用中,发现并不如上所说. 例如 @JSONField(name="project_id") private Long ProjectID 发现bean 转json的时候并是"project_id":xxx的形式,json转bean的时候也不会把"proj

  • FastJson踩坑:@JsonField在反序列化时失效的解决

    问题描述 一个对象(某个字段为枚举类型,为了不采用默认的序列化过程,用@JSONField指定了序列化器和反序列器,过程见旧博文),将其放到JSONArray中再序列化JSONArray对象,用得到的JSON字符串再反序列化时,发现能够正常反序列化出JSONArray,而对JSONArray中的某个元素再反序列化成类对象时,出错. 示例 同样用旧博文的示例做个简单测试. 基本对象类Article. public class Article { private String title; priv

  • mybatis框架order by作为参数传入时失效的解决

    mybatis order by作为参数传入失效 mxl中的语句如下 <select id="statToday" resultType="com.dahua.la.business.model.vo.StatSysResultVO"> select a, b, count(1) as total from table where a is not null and b is not null and operateTime >= #{startT

  • Spring数据库连接池url参数踩坑及解决

    目录 Spring数据库连接池url参数踩坑 遇到的问题 报错情况 解决 修改数据库连接池的url后,还是连接原先的url 问题 例如 Spring数据库连接池url参数踩坑 遇到的问题 报错情况 解决 & ' 字符在xml需要转义为 ' & ' 修改数据库连接池的url后,还是连接原先的url 问题 当修改连接池url之后,访问的还是原来的数据库. 例如 原来: url=jdbc:mysql://192.168.250.227:3306/myshop?characterEncoding=

  • FastJSON字段智能匹配踩坑的解决

    背景 2021年第一天早上,客户突然投诉说系统的一个功能出了问题,紧急排查后发现后端系统确实出了bug,原因为前端传输的JSON报文,后端反序列化成JavaBean后部分字段的值丢失了. 查看git提交历史记录,前端和后端近期并未对该功能的接口字段做任何修改,联想到上个版本升级了后端的FastJSON的版本,怀疑是后端系统对FastJSON升级导致的问题. 复现 @Data static class Label { @JSONField(name = "label_id") priva

  • 基于IOS端微信分享失效的踩坑及解决方法

    最近的一个公众号是基于vue的spa应用,在接入微信分享和微信语音的时候出现了:在Android上一切正常,但是在ios端调用wx.config的时候总是失败,去翻了官方文档也并没有找到解决方案,最后在测试中发现是因为初始化的时候传入的URL的问题.具体过程如下: 微信config接口配置,官方文档如下: 所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支

  • Docker创建容器时目录权限踩坑

    昨天写项目时需要用到Mysql的衍生版本percona, 就想用Doker来安装.结果踩了一晚上坑, 今早终于解决. 现记录在此. 这个坑原因是我对linux的目录权限问题不敏感导致的. 我的系统是ubuntu16.04, 运行 docker pull percona 拉取镜像时一切正常. 拉取完后,输入 docker images查看所有镜像, 显示正常: 然后我创建容器,命令为(执行时不要有换行): docker create --name percona -v /data/mysql-da

  • iOS schem与Universal Link 调试时踩坑解决记录

    目录 简介 AppDelegate和SceneDelegate 问题:在iOS13以上冷启动的时候不会走代理函数! 如果你用了Scheme方式: iOS13之前会走这个代理函数 iOS13之后会走 如果你用了Universal Link方式: iOS13之前会走这个代理函数 iOS13之后会走 总结 简介 scheme和Universal Link是在iOS中两种可以在网页中点击回跳到自己预定的APP的两种方式.至于这两种方式需要怎么配置,这里就不做详细的介绍了.网上的文章一搜一大堆.今天主要是

  • 泛型的类型擦除后fastjson反序列化时如何还原详解

    目录 铺垫 错误写法1 错误写法2 正确写法 TypeReference 验证 扩展 铺垫 在前面的文章中,我们讲过Java中泛型的类型擦除,不过有小伙伴在后台留言提出了一个问题,带有泛型的实体的反序列化过程是如何实现的,今天我们就来看看这个问题. 我们选择fastjson来进行反序列化的测试,在测试前先定义一个实体类: @Data public class Foo<T> { private String val; private T obj; } 如果大家对泛型的类型擦除比较熟悉的话,就会知

  • Vue3+Tsx给路由加切换动画时的踩坑及解决

    目录 项目场景 样式文件 步骤 最终代码 总结 项目场景 用最新的技术栈Vue+Tsx给后台管理系统路由加动画时,语法上与模板语法有些许不同,记录下自己的踩坑记录 样式文件 新建文件transition.scss,这里用的是若依框架人家写好的样式,写好之后在全局引入该样式文件 // global transition css /* fade */ .fade-enter-active, .fade-leave-active { transition: opacity 0.28s; } .fade

  • Java Bean转Map的那些踩坑实战

    目录 一.背景 二.那些坑 2.0 测试对象 2.1 JSON 反序列化了类型丢失 2.1.1 问题复现 2.2.2 问题描述 2.2 BeanMap 转换属性名错误 2.2.1 commons-beanutils 的 BeanMap 2.2.2 使用 cglib 的 BeanMap 三.解决办法 3.1 解决方案 3.2 原理解析 四.总结 一.背景 有些业务场景下需要将 Java Bean 转成 Map 再使用. 以为很简单场景,但是坑很多. 二.那些坑 2.0 测试对象 import lo

随机推荐