10 分鐘輕鬆學會 Jackson 反序列化自動適配子類

程式之心發表於2021-03-17

作者:丁儀

來源:https://chengxuzhixin.com/blog/post/Jackson-fan-xu-lie-hua-zi-dong-shi-pei-zi-lei.html

 

json 格式使用非常方便,通常情況下我們反序列化的時候需要指定具體型別。如果遇到繼承型別可能會解析失敗。今天總結下基於型別擴充套件的子類自動適配,能夠實現反序列化時按需適配子類。

 

比如定義 Model 型別,僅有欄位 key,型別為 String。預設情況下,json 裡面只能配置 Model 已有的欄位,只有類似這樣的資料可以反序列化成功:

{"key": "demo"}

  

如果 Model 是架構底層的定義,並且允許上層應用繼承 Model 實現業務自定義欄位,預設的解析就無法滿足擴充套件需求了。或者 json 資料來自外部,內部需要路由以實現定製,也是無法滿足的。比如,json 資料增加 value 欄位,變成:

{"key": "demo","value": "test"}

   

通常的方案可能是在 Model 增加 value 欄位。對於架構設計和具有良好相容性的程式碼來說,增加 value 欄位不是最合適的。在分層架構中,Model 可能位於底層,或者在引入的 jar 包中,業務無法直接修改欄位定義。此時可以基於 Jackson 的子類適配能力,通過繼承型別實現自定義欄位的反序列化。

 

我們給 Model 型別增加一個欄位 type,加上 Jackson 註解 JsonTypeInfo。在 JsonTypeInfo 中指定子類擴充套件的屬性欄位是 type,和 json 資料中的 type 欄位對應。JsonTypeInfo 中的欄位含義如下:

  • use:指定用哪種方式自動適配子類,這裡設為子類的名稱;
  • property:指定配置子型別的欄位,這裡設為 type 欄位;
  • defaultImpl:未設定 type 時預設的解析型別,這裡設為 Model 本身;
  • visible:反序列化時 property 配置的欄位是否解析出值放在結果中,預設是 false;
@Getter
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", 
              defaultImpl = Model.class, visible = true)
class Model {
    @JsonIgnore
    private String type;
    private String key;
}

   

增加一個 Model 的子類 CustomModel。由 CustomModel 類擴充套件 value 欄位,實現業務擴充套件定製。這裡在專案中增加一個 @JsonTypeDefine 註解來定義 CustomModel 是 Model 的名字為 custom 的子型別擴充套件。程式碼如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JsonTypeDefine {
    String value() default "";
    String desc() default "";
}

@JsonTypeDefine("custom")
class CustomModel extends Model {
    private String value;
}

   

對於上面提到的 json 資料,增加一個 type 欄位,值為 custom。執行時 Jackson 識別到 type 值為 custom ,就會按照 custom 關聯的型別進行解析。基於這樣的型別擴充套件,上層業務可以靈活定製,架構底層可以不感知上層定製。json 資料變更為:

{"type": "custom","key": "demo","value": "test"}

   

要想讓 Jackson 認識 custom 這個名字,需要在系統初始化的時候,掃描到所有的子類定義,並注入到 Jackson 中。如下程式碼實現了對型別擴充套件的掃描和注入。主要分為幾個步驟

  1. 掃描 JsonTypeInfo 定義的基類,如 Model,這裡採用開源庫 Reflections 實現
  2. 掃描子類,如 CustomModel,也採用開源庫 Reflections 實現
  3. 註冊子類,JsonTypeDefine 註解中提取子類名稱,如把 "custom" -> CustomModel 關係注入 Jackson。這裡呼叫 Jackson 的 objectMapper 註冊子型別方法 registerSubtypes 注入型別擴充套件
// 使用開源庫 Reflections 掃描 JsonTypeInfo 定義的基類
Set<Class<?>> types = reflections.getTypesAnnotatedWith(JsonTypeInfo.class);
// 遍歷基類
for (Class<?> type : types) {
    // 使用開源庫 Reflections 掃描子類
    Set<?> clazzs = reflections.getSubTypesOf(type);
    if(CollectionUtils.isEmpty(clazzs)){
        continue;
    }
    // 註冊子類,demo 程式碼,請自行修改
    for (Class<?> clazz : clazzs) {
        // 跳過介面和抽象類
        if(clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())){
            continue;
        }
        // 提取 JsonTypeDefine 註解
        JsonTypeDefine extendClassDefine = clazz.getAnnotation(JsonTypeDefine.class);
        if (extendClassDefine == null) {
            continue;
        }
        // 註冊子型別,使用名稱建立關聯
        objectMapper.registerSubtypes(new NamedType(clazz, extendClassDefine.value()));
    }
}

   

經過以上的系統初始化,Jackson 就已經能夠識別 Model 型別的名字為 custom 的子型別了。在解析時無需特別處理,直接呼叫 Jackson 的反序列化方法即可實現解析。對以下資料的解析,將直接轉換成 CustomModel 型別:

{"type": "custom","key": "demo","value": "test"}

   

推薦閱讀

SpringMVC非同步處理的 5 種方式

Linux Cron 定時任務

人類簡史、軟體架構和中臺

限流演算法探祕

三十而立,如期而至

相關文章