@requestbody泛型映射@JsonTypeInfo多態


原文鏈接:https://www.jianshu.com/p/a21f1633d79c

  • jackson允許配置多態類型處理,當進行反序列話時,JSON數據匹配的對象可能有多個子類型,為了正確的讀取對象的類型,我們需要添加一些類型信息。可以通過下面幾個注解來實現:

  • @JsonTypeInfo
    作用於類/接口,被用來開啟多態類型處理,對基類/接口和子類/實現類都有效

  • @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include = JsonTypeInfo.As.PROPERTY,property = "name")
    這個注解有一些屬性:

    • use:定義使用哪一種類型識別碼,它有下面幾個可選值:
      • JsonTypeInfo.Id.CLASS:使用完全限定類名做識別
      • JsonTypeInfo.Id.MINIMAL_CLASS:若基類和子類在同一包類,使用類名(忽略包名)作為識別碼
      • JsonTypeInfo.Id.NAME:一個合乎邏輯的指定名稱
      • JsonTypeInfo.Id.CUSTOM:自定義識別碼,由@JsonTypeIdResolver對應,稍后解釋
      • JsonTypeInfo.Id.NONE:不使用識別碼
    • include(可選):指定識別碼是如何被包含進去的,它有下面幾個可選值:
      • JsonTypeInfo.As.PROPERTY:作為數據的兄弟屬性
      • JsonTypeInfo.As.EXISTING_PROPERTY:作為POJO中已經存在的屬性
      • JsonTypeInfo.As.EXTERNAL_PROPERTY:作為擴展屬性
      • JsonTypeInfo.As.WRAPPER_OBJECT:作為一個包裝的對象
      • JsonTypeInfo.As.WRAPPER_ARRAY:作為一個包裝的數組
    • property(可選):制定識別碼的屬性名稱
      此屬性只有當:
      • useJsonTypeInfo.Id.CLASS(若不指定property則默認為@class)、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property則默認為@c)、JsonTypeInfo.Id.NAME(若不指定property默認為@type),
      • includeJsonTypeInfo.As.PROPERTYJsonTypeInfo.As.EXISTING_PROPERTYJsonTypeInfo.As.EXTERNAL_PROPERTY時才有效
    • defaultImpl(可選):如果類型識別碼不存在或者無效,可以使用該屬性來制定反序列化時使用的默認類型
    • visible(可選,默認為false):是否可見
      屬性定義了類型標識符的值是否會通過JSON流成為反序列化器的一部分,默認為fale,也就是說,jackson會從JSON內容中處理和刪除類型標識符再傳遞給JsonDeserializer。
  • @JsonSubTypes
    作用於類/接口,用來列出給定類的子類,只有當子類類型無法被檢測到時才會使用它,一般是配合@JsonTypeInfo在基類上使用,比如:

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include = JsonTypeInfo.As.PROPERTY,property = "typeName") @JsonSubTypes({@JsonSubTypes.Type(value=Sub1.class,name = "sub1"),@JsonSubTypes.Type(value=Sub2.class,name = "sub2")}) 

@JsonSubTypes的值是一個@JsonSubTypes.Type[]數組,里面枚舉了多態類型(value對應子類)和類型的標識符值(name對應@JsonTypeInfo中的property標識名稱的值,此為可選值,若不制定需由@JsonTypeName在子類上制定)

  • @JsonTypeName
    作用於子類,用來為多態子類指定類型標識符的值
    比如:
    @JsonTypeName(value = "sub1") 

示例

  • 基類:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,property = "type") @JsonSubTypes({@JsonSubTypes.Type(value= Test.RoleUser.class,name = "role"),@JsonSubTypes.Type(value= Test.TokenUser.class,name = "token")}) public abstract class AbstractBaseEntity { private String userName; private String password; public String getUserName() { return userName; } public AbstractBaseEntity setUserName(String userName) { this.userName = userName; return this; } public String getPassword() { return password; } public AbstractBaseEntity setPassword(String password) { this.password = password; return this; } @Override public String toString() { final StringBuffer sb = new StringBuffer("AbstractBaseEntity{"); sb.append("userName='").append(userName).append('\''); sb.append(", password='").append(password).append('\''); sb.append('}'); return sb.toString(); } } 
  • 測試類
import com.fasterxml.jackson.databind.ObjectMapper; /** * @author micocube * projectName: utils4j * packageName: jackson * email: ldscube@gmail.com * createTime: 2019-07-05 11:23 * version: 0.1 * description: */ public class Test { # 子類 public static class TokenUser extends AbstractBaseEntity { private String token; public String getToken() { return token; } public TokenUser setToken(String token) { this.token = token; return this; } 
  • 程序輸出
{"type":"jackson.Test$RoleUser","userName":"roleUserName","password":"rolePwd","roleName":"role"}
{"type":"jackson.Test$TokenUser","userName":"tokenUserName","password":"tokenPassword","token":"token"}
RoleUser{roleName='role'}AbstractBaseEntity{userName='roleUserName', password='rolePwd'}
TokenUser{token='token'}AbstractBaseEntity{userName='tokenUserName', password='tokenPassword'}

因為基類中use = JsonTypeInfo.Id.CLASS,property = "type",序列化時(輸出的第一行和第二行type值為class限定名),若改為use = JsonTypeInfo.Id.NAME,property = "type",那么輸出如下,use是Name,取值為JsonSubTypes的name,屬性名為type

{"type":"role","userName":"roleUserName","password":"rolePwd","roleName":"role"}
{"type":"token","userName":"tokenUserName","password":"tokenPassword","token":"token"}
RoleUser{roleName='role'}AbstractBaseEntity{userName='roleUserName', password='rolePwd'}
TokenUser{token='token'}AbstractBaseEntity{userName='tokenUserName', password='tokenPassword'}

參考資料

#Jackson對多態和多子類序列化的處理配置

十分鍾學習Jackson多態處理

上案例:

public abstract class Animal { private String name; //忽略getter和setter } public class Elephant extends Animal{ } public class Monkey extends Animal{ } Elephant elephant = new Elephant(); elephant.setName("大象精"); Monkey monkey = new Monkey(); monkey.setName("孫悟空"); ObjectMapper objectMapper = new ObjectMapper(); System.out.println(objectMapper.writeValueAsString(elephant)); System.out.println(objectMapper.writeValueAsString(monkey)); 

輸出:

{"name":"大象精"} {"name":"孫悟空"} 

嗯~ o( ̄▽ ̄)o,是可以序列化,但好像沒有辦法識別他們的類型啊。

  • @JsonTypeInfo注解開啟多態處理

修改下代碼:

@JsonTypeInfo( use=Id.CLASS ) public abstract class Animal { private String name; //忽略getter和setter } 

重新執行,輸出:

{"@class":"com.tinylynn.springboot.json.type.Elephant","name":"大象精"} {"@class":"com.tinylynn.springboot.json.type.Monkey","name":"孫悟空"} 

看到不一樣的東西了,Jackson幫我們添加了一個@class節點,值是完全限定類名

  • @JsonTypeInfo注解的說明

它有一些屬性:

use(必選):定義使用哪一種類型識別碼,可選值有多種:
JsonTypeInfo.Id.NONE:不使用識別碼

{"name":"大象精"} {"name":"孫悟空"} 

JsonTypeInfo.Id.CLASS:使用完全限定類名做識別,識別碼名稱@class

{"@class":"com.tinylynn.springboot.json.type.Elephant","name":"大象精"} {"@class":"com.tinylynn.springboot.json.type.Monkey","name":"孫悟空"} 

JsonTypeInfo.Id.MINIMAL_CLASS:表示具有最小路徑的Java類名稱用作識別,識別碼名稱@c(慎用)

{"@c":".Elephant","name":"大象精"} {"@c":".Monkey","name":"孫悟空"} 

JsonTypeInfo.Id.NAME:使用類型的名稱做識別,識別碼名稱@type

{"@type":"Elephant","name":"大象精"} {"@type":"Monkey","name":"孫悟空"} 

JsonTypeInfo.Id.CUSTOM:自定義識別碼,需結合property屬性和@JsonTypeIdResolver注釋,后面給出案例。

  • include(可選):設置識別碼包含在哪里。

JsonTypeInfo.As.PROPERTY:作為POJO的屬性出現(默認)
JsonTypeInfo.As.WRAPPER_OBJECT:作為一個包裝的對象

@JsonTypeInfo( use=Id.NAME, include=As.WRAPPER_OBJECT ) public abstract class Animal { //... } 

輸出:

{"Elephant":{"name":"大象精"}} {"Monkey":{"name":"孫悟空"}} 

JsonTypeInfo.As.WRAPPER_ARRAY:作為一個包裝的數組

["Elephant",{"name":"大象精"}] ["Monkey",{"name":"孫悟空"}] 

JsonTypeInfo.As.EXTERNAL_PROPERTY:作為一個額外的屬性,跟POJO同級,只能用於屬性;如何作用於類則跟JsonTypeInfo.As.PROPERTY是相同效果。上案例:

public class Zoo { @JsonTypeInfo(use=Id.NAME,include=As.EXTERNAL_PROPERTY) private Animal animal; //動物園名稱 private String name; //忽略getter和setter } Zoo zoo = new Zoo(); zoo.setName("只有一只大象的動物園"); zoo.setAnimal(elephant); System.out.println(objectMapper.writeValueAsString(zoo)); 

輸出:

{"animal":{"name":"大象精"},"@type":"Elephant","name":"只有一只大象的動物園"} 

JsonTypeInfo.As.EXISTING_PROPERTY:反序列化的時候,跟JsonTypeInfo.As.PROPERTY的處理相同;序列化,則Jackson不主動處理,由我們自行處理。
序列化:POJO -> JSON
反序列化:JSON -> POJO
上案例就清楚啦:

@JsonTypeInfo( use=Id.NAME, include=As.EXISTING_PROPERTY, property="type" //設置識別碼名稱為type,跟字段type名稱一樣。 ) @JsonSubTypes({ //設置對應子類的識別碼值 @Type(value = Monkey.class, name = "猴子") , @Type(value = Elephant.class, name = "大象") }) public abstract class Animal { private String type; //新增類型 private String name; //忽略getter和setter } ObjectMapper objectMapper = new ObjectMapper(); Elephant elephant = new Elephant(); elephant.setName("孤單的大象"); String elephantJson = objectMapper.writeValueAsString(elephant); System.out.println(elephantJson); Elephant anotherElephant = new Elephant(); anotherElephant.setName("另一頭孤單的大象"); anotherElephant.setType("大象"); String anotherElephantJson = objectMapper.writeValueAsString(anotherElephant); System.out.println(anotherElephantJson); 

輸出:

{"type":null,"name":"孤單的大象"} {"type":"大象","name":"另一頭孤單的大象"} 

說明include=As.EXISTING_PROPERTY在序列化的時候Jackson不會處理識別碼。

String deElephant = "{\"type\":\"大象\",\"name\":\"另一頭孤單的大象\"}"; Animal elephant = objectMapper.readValue(deElephant, Animal.class); System.out.println(elephant instanceof Elephant); //true 

在反序列化時候,type的值被認為是識別碼,如果type的值不是[大象,猴子]其中之一,則程序會拋出異常。

property(可選):設置識別碼是名稱,在include=JsonTypeInfo.As.PROPERTY或use=JsonTypeInfo.Id.CUSTOM生效。其他情況使用默認的識別碼名稱。
注意:include=JsonTypeInfo.As.PROPERTY和property同時存在有個問題,如果POJO具有相同名稱的屬性,會出現兩個..

上案例:

@JsonTypeInfo( use=Id.NAME, include=As.PROPERTY, property="type" //設置識別碼名稱為type,跟字段type名稱一樣。 ) @JsonSubTypes({ //設置對應子類的識別碼值 @Type(value = Monkey.class, name = "猴子") , @Type(value = Elephant.class, name = "大象") }) public abstract class Animal { private String type; private String name; //忽略getter和setter } Elephant elephant = new Elephant(); elephant.setType("猴子");//故意設置 elephant.setName("我是什么動物"); ObjectMapper objectMapper = new ObjectMapper(); System.out.println(objectMapper.writeValueAsString(elephant)); 

輸出:

{"type":"大象","type":"猴子","name":"我是什么動物"} 

好神奇,兩個type。其實好理解啦,include=As.PROPERTY告訴Jackson 識別碼是作為POJO的屬性出現,而同時你告訴Jackson識別碼名稱為type,Jackson才不管你POJO是不是已經有包含type屬性,都給你輸出。如果沒有設置property屬性,則使用默認的識別碼名稱,就是@type。
比這個更神奇的,上案例:

Elephant elephant = new Elephant(); elephant.setType("我是大象"); elephant.setName("安安"); Monkey monkey = new Monkey(); monkey.setType("我是猴子"); monkey.setName("寧寧"); List<Animal> list = Lists.newArrayList(elephant, monkey); ObjectMapper objectMapper = new ObjectMapper(); System.out.println(objectMapper.writeValueAsString(list)); 

輸出:

[{"type":"我是大象","name":"安安"},{"type":"我是猴子","name":"寧寧"}] 

沒有識別碼,被吃掉了!!!什么原因不知道,記住這個坑就好了!!

visible(可選):定義識別碼在反序列化時是否保留(不管false或true都不影響序列化)。默認是false,表示Jackson可以將識別碼識別為類型后就刪除。
看不懂,上案例:

@JsonTypeInfo( use = Id.NAME, include = As.PROPERTY, property = "type", //跟type屬性同名, visible = false ) @JsonSubTypes({ @Type(value = Monkey.class, name = "猴子"), @Type(value = Elephant.class, name = "大象") }) public abstract class Animal { private String type; private String name; //忽略getter和setter } Elephant elephant = new Elephant(); elephant.setName("安安"); ObjectMapper objectMapper = new ObjectMapper(); //序列化,注意這邊是會出現兩個type String json = objectMapper.writeValueAsString(elephant); System.out.println(json); //反序列化,注意這邊只有一個type,但它是作為識別碼被Jackson識別的 String deJson = "{\"type\":\"大象\",\"name\":\"安安\"}"; Animal animal = objectMapper.readValue(deJson, Animal.class); System.out.println(animal instanceof Elephant); System.out.println(animal.getType()); 

輸出:

{"type":"大象","type":null,"name":"安安"} true //說明識別碼是有效的 null //說明Jackson處理完識別碼就刪除了 

將visible改為true再執行一遍:

{"type":"大象","type":null,"name":"安安"} true 大象 

總結下,visible=true和include=As.EXISTING_PROPERTY配合比較好。上面有提到@JsonSubTypes,那么這個注解做什么的呢。

  • @JsonSubTypes注解的說明
    和@JsonTypeInfo一起使用的注解,聲明可序列化多態類型的子類型,以及名稱。只有和use = Id.NAME,才會使用@JsonSubTypes定義的名稱。
    使用@JsonTypeName注解

@JsonSubTypes作用在超類,有時候開發並沒有辦法可以直接修改,那么新增的子類要如何定義呢?
上案例:

輸出:

{"type":"國寶熊貓","type":null,"name":"貝貝"}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM