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

  • 2021 年 3 月 17 日
  • 筆記

作者:丁儀

來源://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 定時任務

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

限流演算法探秘

三十而立,如期而至

Tags: