FastJson踩坑:@JsonField在反序列化時(shí)失效的解決
問題描述
一個(gè)對象(某個(gè)字段為枚舉類型,為了不采用默認(rèn)的序列化過程,用@JSONField指定了序列化器和反序列器,過程見舊博文),將其放到JSONArray中再序列化JSONArray對象,用得到的JSON字符串再反序列化時(shí),發(fā)現(xiàn)能夠正常反序列化出JSONArray,而對JSONArray中的某個(gè)元素再反序列化成類對象時(shí),出錯(cuò)。
示例
同樣用舊博文的示例做個(gè)簡單測試。
基本對象類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中,可以從結(jié)果中看出序列化和反序列化過程均正常。
而testJSONArrayParse先把類對象放到JSONArray中,在從JSONArray中取出對象反序列化,反序列化的結(jié)果就不正常了。
疑問
為什么JSONObject和JSONArray的反序列化過程得到的結(jié)果不一致?兩者的反序列過程差異在哪?
DEBUG
遇事不決,開始DEBUG。
JSON.parseObject的流程
首先,JSON是一個(gè)門面類,提供出一些靜態(tài)的方法供外部使用。比如說parseObject()方法。其內(nèi)部會創(chuàng)建解析器DefaultJSONParser,并將解析委托給解析器執(zhí)行。
DefualtJSONParser在創(chuàng)建時(shí)接受輸入,全局配置及特性,相當(dāng)于獲取到了本次解析所有的數(shù)據(jù)。同時(shí)DefualtJSONParser的內(nèi)部創(chuàng)建了一些用于解析的組件,例如JSONLexer(用于字符串解析)。解析過程在parseObject中執(zhí)行,parseObject會通過ParseConfig(保存解析配置的一個(gè)全局對象)獲取到解析器ObjectDeserializer,并由解析器處理真正的解析過程。
在通過Class獲取ObjectDeserializer時(shí),首先會確定ParserConfig中是否緩存了對應(yīng)的反序列化器,如果不存在,則會新建一個(gè)JavaBeanDeserializer(對于一般Java對象而言)。在新建過程中,會解析Class的屬性,并保存在JavaBeanInfo中。 解析器的解析過程就是對比JSON字符串中的KEY和JavaBeanInfo的過程,把對應(yīng)的值反序列化出來(判斷是否有JSONField注解,并根據(jù)注解的屬性處理也在這一步),最終還原對象。
以流程圖表示上述過程:

JSONArray.getObject()
JSONArray.getObject()會先從JSONArray中獲取出Object,然后調(diào)用TypeUtils對Object通過TypeUtils.castToJavaBean()轉(zhuǎn)型。
TypeUtils通過根據(jù)需要轉(zhuǎn)型的類型從ParserConfig中獲取ObjectDeserializer反序列化器,對于普通 Java Bean 而言,是JavaBeanDeserializer。
由于JSONArray中取出的Object實(shí)際上是JSONObject對象,因此會由JavaBeanDeserializer反序列化器的createInstance()方法執(zhí)行反序列化,得到對象。
以流程圖表示上述過程:

deserialize 和 createInstance 的不同
deserialize在反序列化時(shí),會從class上獲取更多的屬性,其中就包括JSONField注解上的信息,而createInstance獲取的信息較少,因此忽略JSONField所帶的信息,導(dǎo)致自定義的反序列化器在反序列化時(shí)失效。
疑惑
為什么都是反序列化過程,二者在行為和表現(xiàn)上會有所不同?官方是如何定義deserialize和createInstance的?
上述這些問題還需要查詢更多資料來明確。也希望了解緣由的讀者進(jìn)行告知。
問題的解決方式
解決的辦法不先轉(zhuǎn)換成JSONArray,然后再反序列化對象。而是通過JSON.parseArray直接轉(zhuǎn)成對象的List。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java 獲取服務(wù)器真實(shí)IP的實(shí)例
這篇文章主要介紹了java 獲取服務(wù)器真實(shí)IP的實(shí)例的相關(guān)資料,這里提供實(shí)現(xiàn)方法幫助大家學(xué)習(xí)理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08
java 自己實(shí)現(xiàn)DataSource實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了java 自己實(shí)現(xiàn)DataSource實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05
Mybatis select記錄封裝的實(shí)現(xiàn)
這篇文章主要介紹了Mybatis select記錄封裝的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
關(guān)于Lombok @Data注解:簡化Java代碼的魔法棒
Lombok庫通過@Data注解自動生成常見的樣板代碼如getter、setter、toString等,極大減少代碼量,提高開發(fā)效率,@Data注解集成了@ToString、@EqualsAndHashCode、@Getter、@Setter、@RequiredArgsConstructor等注解的功能2024-10-10
Java 內(nèi)置接口 Serializable示例詳解
這篇文章主要為大家介紹了Java 內(nèi)置接口 Serializable示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

