Java Jackson @JsonTypeInfo 多態(tài)類型處理

  • jackson允許配置多態(tài)類型處理,當(dāng)進(jìn)行反序列話時(shí),JSON數(shù)據(jù)匹配的對(duì)象可能有多個(gè)子類型,為了正確的讀取對(duì)象的類型,我們需要添加一些類型信息。可以通過(guò)下面幾個(gè)注解來(lái)實(shí)現(xiàn):

  • @JsonTypeInfo
    作用于類/接口,被用來(lái)開(kāi)啟多態(tài)類型處理,對(duì)基類/接口和子類/實(shí)現(xiàn)類都有效

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

    • use:定義使用哪一種類型識(shí)別碼,它有下面幾個(gè)可選值:
      • JsonTypeInfo.Id.CLASS:使用完全限定類名做識(shí)別
      • JsonTypeInfo.Id.MINIMAL_CLASS:若基類和子類在同一包類,使用類名(忽略包名)作為識(shí)別碼
      • JsonTypeInfo.Id.NAME:一個(gè)合乎邏輯的指定名稱
      • JsonTypeInfo.Id.CUSTOM:自定義識(shí)別碼,由@JsonTypeIdResolver對(duì)應(yīng),稍后解釋
      • JsonTypeInfo.Id.NONE:不使用識(shí)別碼
    • include(可選):指定識(shí)別碼是如何被包含進(jìn)去的,它有下面幾個(gè)可選值:
      • JsonTypeInfo.As.PROPERTY:作為數(shù)據(jù)的兄弟屬性
      • JsonTypeInfo.As.EXISTING_PROPERTY:作為POJO中已經(jīng)存在的屬性
      • JsonTypeInfo.As.EXTERNAL_PROPERTY:作為擴(kuò)展屬性
      • JsonTypeInfo.As.WRAPPER_OBJECT:作為一個(gè)包裝的對(duì)象
      • JsonTypeInfo.As.WRAPPER_ARRAY:作為一個(gè)包裝的數(shù)組
    • property(可選):制定識(shí)別碼的屬性名稱
      此屬性只有當(dāng):
      • useJsonTypeInfo.Id.CLASS(若不指定property則默認(rèn)為@class)、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property則默認(rèn)為@c)、JsonTypeInfo.Id.NAME(若不指定property默認(rèn)為@type),
      • includeJsonTypeInfo.As.PROPERTYJsonTypeInfo.As.EXISTING_PROPERTYJsonTypeInfo.As.EXTERNAL_PROPERTY時(shí)才有效
    • defaultImpl(可選):如果類型識(shí)別碼不存在或者無(wú)效,可以使用該屬性來(lái)制定反序列化時(shí)使用的默認(rèn)類型
    • visible(可選,默認(rèn)為false):是否可見(jiàn)
      屬性定義了類型標(biāo)識(shí)符的值是否會(huì)通過(guò)JSON流成為反序列化器的一部分,默認(rèn)為fale,也就是說(shuō),jackson會(huì)從JSON內(nèi)容中處理和刪除類型標(biāo)識(shí)符再傳遞給JsonDeserializer。
  • @JsonSubTypes
    作用于類/接口,用來(lái)列出給定類的子類,只有當(dāng)子類類型無(wú)法被檢測(cè)到時(shí)才會(huì)使用它,一般是配合@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的值是一個(gè)@JsonSubTypes.Type[]數(shù)組,里面枚舉了多態(tài)類型(value對(duì)應(yīng)子類)和類型的標(biāo)識(shí)符值(name對(duì)應(yīng)@JsonTypeInfo中的property標(biāo)識(shí)名稱的值,此為可選值,若不制定需由@JsonTypeName在子類上制定)

  • @JsonTypeName
    作用于子類,用來(lái)為多態(tài)子類指定類型標(biāo)識(shí)符的值
    比如:
    @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();
    }
}

  • 測(cè)試類
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;
        }

        @Override
        public String toString() {
            final StringBuffer sb = new StringBuffer("TokenUser{");
            sb.append("token='").append(token).append('\'');
            sb.append('}');
            sb.append(super.toString());
            return sb.toString();
        }
    }

    # 子類
    public static class RoleUser extends AbstractBaseEntity {

        private String roleName;


        public String getRoleName() {
            return roleName;
        }

        public RoleUser setRoleName(String roleName) {
            this.roleName = roleName;
            return this;
        }

        @Override
        public String toString() {
            final StringBuffer sb = new StringBuffer("RoleUser{");
            sb.append("roleName='").append(roleName).append('\'');
            sb.append('}');
            sb.append(super.toString());
            return sb.toString();
        }
    }







    public static void main(String[] args) throws Exception{
        ObjectMapper objectMapper = new ObjectMapper();

        RoleUser roleUser = new RoleUser();
        roleUser.setRoleName("role");
        roleUser.setPassword("rolePwd");
        roleUser.setUserName("roleUserName");


        TokenUser tokenUser = new TokenUser();
        tokenUser.setToken("token");
        tokenUser.setPassword("tokenPassword");
        tokenUser.setUserName("tokenUserName");

        String roleStr = objectMapper.writeValueAsString(roleUser);
        String tokenStr = objectMapper.writeValueAsString(tokenUser);

        System.out.println(roleStr);
        System.out.println(tokenStr);


        AbstractBaseEntity roleEntity = objectMapper.readValue(roleStr, AbstractBaseEntity.class);
        AbstractBaseEntity tokenEntity = objectMapper.readValue(tokenStr, AbstractBaseEntity.class);


        System.out.println(roleEntity);
        System.out.println(tokenEntity);

    }
}

  • 程序輸出
{"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'}

因?yàn)榛愔?code>use = JsonTypeInfo.Id.CLASS,property = "type",序列化時(shí)(輸出的第一行和第二行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對(duì)多態(tài)和多子類序列化的處理配置

十分鐘學(xué)習(xí)Jackson多態(tài)處理

上案例:

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,是可以序列化,但好像沒(méi)有辦法識(shí)別他們的類型啊。

  • @JsonTypeInfo注解開(kāi)啟多態(tài)處理

修改下代碼:

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

重新執(zhí)行,輸出:

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

看到不一樣的東西了,Jackson幫我們添加了一個(gè)@class節(jié)點(diǎn),值是完全限定類名

  • @JsonTypeInfo注解的說(shuō)明

它有一些屬性:

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

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

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

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

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

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

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

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

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

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

JsonTypeInfo.As.PROPERTY:作為POJO的屬性出現(xiàn)(默認(rèn))
JsonTypeInfo.As.WRAPPER_OBJECT:作為一個(gè)包裝的對(duì)象

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

輸出:

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

JsonTypeInfo.As.WRAPPER_ARRAY:作為一個(gè)包裝的數(shù)組

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

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

public class Zoo {
    @JsonTypeInfo(use=Id.NAME,include=As.EXTERNAL_PROPERTY)
    private Animal animal;

    //動(dòng)物園名稱
    private String name;
    //忽略getter和setter
}

Zoo zoo = new Zoo();
zoo.setName("只有一只大象的動(dòng)物園");
zoo.setAnimal(elephant);
System.out.println(objectMapper.writeValueAsString(zoo));

輸出:

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

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

@JsonTypeInfo(
    use=Id.NAME,
    include=As.EXISTING_PROPERTY,
    property="type" //設(shè)置識(shí)別碼名稱為type,跟字段type名稱一樣。
)
@JsonSubTypes({ //設(shè)置對(duì)應(yīng)子類的識(shí)別碼值
    @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":"另一頭孤單的大象"}

說(shuō)明include=As.EXISTING_PROPERTY在序列化的時(shí)候Jackson不會(huì)處理識(shí)別碼。

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

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

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

上案例:

@JsonTypeInfo(
    use=Id.NAME,
    include=As.PROPERTY,
    property="type" //設(shè)置識(shí)別碼名稱為type,跟字段type名稱一樣。
)
@JsonSubTypes({ //設(shè)置對(duì)應(yīng)子類的識(shí)別碼值
    @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("猴子");//故意設(shè)置
elephant.setName("我是什么動(dòng)物");
ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.writeValueAsString(elephant));

輸出:

{"type":"大象","type":"猴子","name":"我是什么動(dòng)物"}

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

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":"寧寧"}]

沒(méi)有識(shí)別碼,被吃掉了!!!什么原因不知道,記住這個(gè)坑就好了!!

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

@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();
//序列化,注意這邊是會(huì)出現(xiàn)兩個(gè)type
String json = objectMapper.writeValueAsString(elephant); 
System.out.println(json);

//反序列化,注意這邊只有一個(gè)type,但它是作為識(shí)別碼被Jackson識(shí)別的
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 //說(shuō)明識(shí)別碼是有效的
null //說(shuō)明Jackson處理完識(shí)別碼就刪除了

將visible改為true再執(zhí)行一遍:

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

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

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

@JsonSubTypes作用在超類,有時(shí)候開(kāi)發(fā)并沒(méi)有辦法可以直接修改,那么新增的子類要如何定義呢?
上案例:

@JsonTypeName("國(guó)寶熊貓")
public class Panda extends Animal{
}

Panda panda = new Panda();
panda.setName("貝貝");
ObjectMapper objectMapper = new ObjectMapper();
//序列化
String json = objectMapper.writeValueAsString(panda);
System.out.println(json);

輸出:

{"type":"國(guó)寶熊貓","type":null,"name":"貝貝"}

參考鏈接:
十分鐘學(xué)習(xí)Jackson多態(tài)處理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容