问题描述

一个对象(某个字段为枚举类型,为了不采用默认的序列化过程,用@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中取出对象反序列化,反序列化的结果就不正常了。

疑问

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

DEBUG

遇事不决,开始DEBUG

JSON.parseObject的流程

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

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

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

以流程图表示上述过程:

JSONArray.getObject()

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

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

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

以流程图表示上述过程:

deserialize 和 createInstance 的不同

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

疑惑

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

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

问题的解决方式

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

FastJson踩坑:@JsonField在反序列化时失效的更多相关文章

  1. 记一次踩坑:使用ksoap-android时造成的okhttp依赖冲突问题

    项目中需要调用webservice接口,android SDK中并没有直接访问webservice接口的方法,于是我引入了ksoap-android的jar包,来实现访问webservice接口.刚开 ...

  2. 小程序踩坑异步请求json时,headers设置 "content-type": "application/x-www-form-urlencoded"

    wx.request({ url: url, method:params.method, data: params.data, header: { "content-type": ...

  3. 区块链之智能合约 solidity踩坑 --上篇

    概述 最近在写合约时遇到一些坑,做一下总结: 介绍主要分一下三个方面: 对区块链的简单描述 结合业务场景,编写简单智能合约,时遇到的坑(上篇) assembly 的使用说明(下篇) 正文 进入正题之前 ...

  4. 【踩坑】利用fastjson反序列化需要默认构造函数

    利用 fastjson等 反序列化时需要注意,他可能会用到 默认的构造函数,如果没有默认构造函数,某些场景下可能会出现 反序列化熟悉为空的情况,如下图所示:

  5. 泛型的类型擦除后,fastjson反序列化时如何还原?

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 哈喽大家好啊,我是Hydra~ 在前面的文章中,我们讲过Java中泛型的类型擦除,不过有小伙伴在后台留言提出了一个问题,带有泛型的实体的反序列化 ...

  6. 新人踩坑的一天——springboot注入mapper时出现java.lang.NullPointerException: null

    来公司的第二周接到了定时任务的开发需求:每天早上十点发送用户报表邮件 .校招新人菜鸟没做过这玩意有些懵(尴尬)于是决定分步写,从excel导出->邮件发送->定时器实现->mappe ...

  7. python踩坑系列之导入包时下划红线及报错“No module named”问题

    python踩坑系列之导入包时下划红线及报错“No module named”问题 使用pycharm编写Python时,自己写了一个包(commontool),在同级另一个路径下(fileshand ...

  8. redis从入门到踩坑

    背景 Redis在互联网项目的使用也是非常普遍的,作为最常用的NO-SQL数据库,对Redis的了解已经成为了后端开发的必备技能.小编对Redis的使用时间不长,但是项目中确两次踩中了Redis的坑, ...

  9. Spark踩坑记——Spark Streaming+Kafka

    [TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...

随机推荐

  1. 1051 Pop Sequence (25分)

    Given a stack which can keep M numbers at most. Push N numbers in the order of 1, 2, 3, ..., N and p ...

  2. php 分页使用limit还是用mysql_data_seek()呢?

    目前大部分教程中介绍的时LIMIT方法,使用这种方法要认识到以下几点: (1)limit不是标准的sql语句; (2)如果选择使用limit,那么您就必须首先向数据库发送一个查询语句来获取记录的总数, ...

  3. go 基本包

    像 fmt.os 等这样具有常用功能的内置包在 Go 语言中有 150 个以上,它们被称为标准库,大部分(一些底层的除外)内置于 Go 本身 unsafe: 包含了一些打破 Go 语言“类型安全”的命 ...

  4. Java中的垃圾回收算法详解

    一.前言   前段时间大致看了一下<深入理解Java虚拟机>这本书,对相关的基础知识有了一定的了解,准备写一写JVM的系列博客,这是第二篇.这篇博客就来谈一谈JVM中使用到的垃圾回收算法. ...

  5. Git应用详解第八讲:Git标签、别名与Git gc

    前言 前情提要:Git应用详解第七讲:Git refspec与远程分支的重要操作 这一节主要介绍Git标签.别名与Git的垃圾回收机制. 一.Git标签(tag) 1.标签的实质 标签与分支十分相似, ...

  6. 动手学Transformer

    动手实现Transformer,所有代码基于tensorflow2.0,配合illustrated-transformer更香. 模型架构 Encoder+Decoder Encoder Decode ...

  7. 试验使用t检验

    官方解释 Excel中使用T.TEST函数 T.TEST(array1,array2,tails,type) Array1      必需.第一个数据集. Array2      必需.第二个数据集. ...

  8. 我用Python一键保存了半佛老师所有的骚气表情包

    本文首发于公众号「Python知识圈」,如需转载,请在公众号联系作者授权. 2019年发现两个有意思而且内容比较硬核的公众号.都是同一个人运营的,我们都叫他半佛老师,现实中的职业是风控,公众号内容涉及 ...

  9. 胜利大逃亡 BFS

    Ignatius被魔王抓走了,有一天魔王出差去了,这可是Ignatius逃亡的好机会. 魔王住在一个城堡里,城堡是一个A*B*C的立方体,可以被表示成A个B*C的矩阵,刚开始Ignatius被关在(0 ...

  10. ATcoder--D - Summer Vacation

    这个题目的题意有点难搞 题目连接: https://atcoder.jp/contests/abc137/tasks/abc137_d 题目大意:输入n和m 指的是一共有n个输入在m天前一共能赚到的钱 ...