jackson允許配置多態(tài)類型處理,當進行反序列話時,JSON數(shù)據(jù)匹配的對象可能有多個子類型,為了正確的讀取對象的類型,我們需要添加一些類型信息??梢酝ㄟ^下面幾個注解來實現(xiàn):
@JsonTypeInfo
作用于類/接口,被用來開啟多態(tài)類型處理,對基類/接口和子類/實現(xiàn)類都有效-
@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對應(yīng),稍后解釋 -
JsonTypeInfo.Id.NONE:不使用識別碼
-
- include(可選):指定識別碼是如何被包含進去的,它有下面幾個可選值:
- JsonTypeInfo.As.PROPERTY:作為數(shù)據(jù)的兄弟屬性
- JsonTypeInfo.As.EXISTING_PROPERTY:作為POJO中已經(jīng)存在的屬性
- JsonTypeInfo.As.EXTERNAL_PROPERTY:作為擴展屬性
- JsonTypeInfo.As.WRAPPER_OBJECT:作為一個包裝的對象
- JsonTypeInfo.As.WRAPPER_ARRAY:作為一個包裝的數(shù)組
- property(可選):制定識別碼的屬性名稱
此屬性只有當:-
use為JsonTypeInfo.Id.CLASS(若不指定property則默認為@class)、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property則默認為@c)、JsonTypeInfo.Id.NAME(若不指定property默認為@type), -
include為JsonTypeInfo.As.PROPERTY、JsonTypeInfo.As.EXISTING_PROPERTY、JsonTypeInfo.As.EXTERNAL_PROPERTY時才有效
-
-
defaultImpl(可選):如果類型識別碼不存在或者無效,可以使用該屬性來制定反序列化時使用的默認類型 - visible(可選,默認為false):是否可見
屬性定義了類型標識符的值是否會通過JSON流成為反序列化器的一部分,默認為fale,也就是說,jackson會從JSON內(nèi)容中處理和刪除類型標識符再傳遞給JsonDeserializer。
- use:定義使用哪一種類型識別碼,它有下面幾個可選值:
-
@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[]數(shù)組,里面枚舉了多態(tài)類型(value對應(yīng)子類)和類型的標識符值(name對應(yīng)@JsonTypeInfo中的property標識名稱的值,此為可選值,若不制定需由@JsonTypeName在子類上制定)
-
@JsonTypeName
作用于子類,用來為多態(tài)子類指定類型標識符的值
比如:@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;
}
@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'}
因為基類中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多態(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,是可以序列化,但好像沒有辦法識別他們的類型啊。
- @JsonTypeInfo注解開啟多態(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幫我們添加了一個@class節(jié)點,值是完全限定類名
- @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:自定義識別碼,需結(jié)合property屬性和@JsonTypeIdResolver注釋,后面給出案例。
- include(可選):設(shè)置識別碼包含在哪里。
JsonTypeInfo.As.PROPERTY:作為POJO的屬性出現(xiàn)(默認)
JsonTypeInfo.As.WRAPPER_OBJECT:作為一個包裝的對象
@JsonTypeInfo(
use=Id.NAME,
include=As.WRAPPER_OBJECT
)
public abstract class Animal {
//...
}
輸出:
{"Elephant":{"name":"大象精"}}
{"Monkey":{"name":"孫悟空"}}
JsonTypeInfo.As.WRAPPER_ARRAY:作為一個包裝的數(shù)組
["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" //設(shè)置識別碼名稱為type,跟字段type名稱一樣。
)
@JsonSubTypes({ //設(shè)置對應(yīng)子類的識別碼值
@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(可選):設(shè)置識別碼是名稱,在include=JsonTypeInfo.As.PROPERTY或use=JsonTypeInfo.Id.CUSTOM生效。其他情況使用默認的識別碼名稱。
注意:include=JsonTypeInfo.As.PROPERTY和property同時存在有個問題,如果POJO具有相同名稱的屬性,會出現(xiàn)兩個..
上案例:
@JsonTypeInfo(
use=Id.NAME,
include=As.PROPERTY,
property="type" //設(shè)置識別碼名稱為type,跟字段type名稱一樣。
)
@JsonSubTypes({ //設(shè)置對應(yīng)子類的識別碼值
@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("我是什么動物");
ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.writeValueAsString(elephant));
輸出:
{"type":"大象","type":"猴子","name":"我是什么動物"}
好神奇,兩個type。其實好理解啦,include=As.PROPERTY告訴Jackson 識別碼是作為POJO的屬性出現(xiàn),而同時你告訴Jackson識別碼名稱為type,Jackson才不管你POJO是不是已經(jīng)有包含type屬性,都給你輸出。如果沒有設(shè)置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":"寧寧"}]
沒有識別碼,被吃掉了?。?!什么原因不知道,記住這個坑就好了?。?/p>
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();
//序列化,注意這邊是會出現(xiàn)兩個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再執(zhí)行一遍:
{"type":"大象","type":null,"name":"安安"}
true
大象
總結(jié)下,visible=true和include=As.EXISTING_PROPERTY配合比較好。上面有提到@JsonSubTypes,那么這個注解做什么的呢。
- @JsonSubTypes注解的說明
和@JsonTypeInfo一起使用的注解,聲明可序列化多態(tài)類型的子類型,以及名稱。只有和use = Id.NAME,才會使用@JsonSubTypes定義的名稱。
使用@JsonTypeName注解
@JsonSubTypes作用在超類,有時候開發(fā)并沒有辦法可以直接修改,那么新增的子類要如何定義呢?
上案例:
@JsonTypeName("國寶熊貓")
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":"國寶熊貓","type":null,"name":"貝貝"}
參考鏈接:
十分鐘學習Jackson多態(tài)處理