FastJson踩坑:@JsonField在反序列化時失效的解決
問題描述
一個對象(某個字段為枚舉類型,為了不采用默認(rèn)的序列化過程,用@JSONField指定了序列化器和反序列器,過程見舊博文),將其放到JSONArray中再序列化JSONArray對象,用得到的JSON字符串再反序列化時,發(fā)現(xiàn)能夠正常反序列化出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中,可以從結(jié)果中看出序列化和反序列化過程均正常。
而testJSONArrayParse先把類對象放到JSONArray中,在從JSONArray中取出對象反序列化,反序列化的結(jié)果就不正常了。
疑問
為什么JSONObject和JSONArray的反序列化過程得到的結(jié)果不一致?兩者的反序列過程差異在哪?
DEBUG
遇事不決,開始DEBUG。
JSON.parseObject
的流程
首先,JSON是一個門面類,提供出一些靜態(tài)的方法供外部使用。比如說parseObject()方法。其內(nèi)部會創(chuàng)建解析器DefaultJSONParser,并將解析委托給解析器執(zhí)行。
DefualtJSONParser在創(chuàng)建時接受輸入,全局配置及特性,相當(dāng)于獲取到了本次解析所有的數(shù)據(jù)。同時DefualtJSONParser的內(nèi)部創(chuàng)建了一些用于解析的組件,例如JSONLexer(用于字符串解析)。解析過程在parseObject中執(zhí)行,parseObject會通過ParseConfig(保存解析配置的一個全局對象)獲取到解析器ObjectDeserializer,并由解析器處理真正的解析過程。
在通過Class獲取ObjectDeserializer時,首先會確定ParserConfig中是否緩存了對應(yīng)的反序列化器,如果不存在,則會新建一個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在反序列化時,會從class上獲取更多的屬性,其中就包括JSONField注解上的信息,而createInstance獲取的信息較少,因此忽略JSONField所帶的信息,導(dǎo)致自定義的反序列化器在反序列化時失效。
疑惑
為什么都是反序列化過程,二者在行為和表現(xiàn)上會有所不同?官方是如何定義deserialize和createInstance的?
上述這些問題還需要查詢更多資料來明確。也希望了解緣由的讀者進(jìn)行告知。
問題的解決方式
解決的辦法不先轉(zhuǎn)換成JSONArray,然后再反序列化對象。而是通過JSON.parseArray直接轉(zhuǎn)成對象的List。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java中form以post、get方式提交數(shù)據(jù)中文亂碼問題總結(jié)
這篇文章主要介紹了java中form以post、get方式提交數(shù)據(jù)中文亂碼問題總結(jié),需要的朋友可以參考下2014-10-10Spring Cloud Stream如何實(shí)現(xiàn)服務(wù)之間的通訊
這篇文章主要介紹了Spring Cloud Stream如何實(shí)現(xiàn)服務(wù)之間的通訊,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10Spring中實(shí)現(xiàn)的三種異步流式接口方法
在現(xiàn)代Web開發(fā)中,接口超時是一個常見的問題,尤其是在處理耗時操作時,傳統(tǒng)的同步接口在處理長時間任務(wù)時會阻塞請求線程,從而影響系統(tǒng)的響應(yīng)能力,本文將詳細(xì)講解Spring中實(shí)現(xiàn)的三種異步流式接口方法,需要的朋友可以參考下2024-10-10IDEA設(shè)置JVM運(yùn)行參數(shù)的方法步驟
這篇文章主要介紹了IDEA設(shè)置JVM運(yùn)行參數(shù)的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08