首頁 > 軟體

FastJson踩坑:@JsonField在反序列化時失效的解決

2021-06-18 16:01:43

問題描述

一個物件(某個欄位為列舉型別,為了不採用預設的序列化過程,用@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。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com