10 分鐘輕鬆學會 Jackson 反序列化自動適配子類
作者:丁儀
來源: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 中。如下程式碼實現了對型別擴充套件的掃描和注入。主要分為幾個步驟
- 掃描 JsonTypeInfo 定義的基類,如 Model,這裡採用開源庫 Reflections 實現;
- 掃描子類,如 CustomModel,也採用開源庫 Reflections 實現;
- 註冊子類,從 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 定時任務
人類簡史、軟體架構和中臺
限流演算法探祕
三十而立,如期