1.場(chǎng)景描述
JSON作為一種輕量級(jí)的數(shù)據(jù)交換格式,其清晰和簡(jiǎn)潔的結(jié)構(gòu)能夠輕松地與Java對(duì)象產(chǎn)生映射關(guān)系。例如,一個(gè)Coke(可口可樂(lè))類的java代碼如下:
public class Coke{
String name = "Coke";
int capacity= 500;
}
用json描述該類:
{
"name":"Coke",
"capacity":500
}
而這種映射關(guān)系可以通過(guò)代碼進(jìn)行轉(zhuǎn)換,也就是所謂的json序列化和反序列化。
序列化:是指將Java對(duì)象轉(zhuǎn)換成Json文件或者Json字符串;
反序列化:是指將Json文件或者Json字符串轉(zhuǎn)換成Java對(duì)象。
Java代碼實(shí)現(xiàn)Json的序列化和反序列化并不難,尤其是現(xiàn)在的很多框架簡(jiǎn)化了很多的過(guò)程。下面以我常用的jackson為例,實(shí)現(xiàn)簡(jiǎn)單的json序列化和反序列化:
Coke類的定義如下
public class Coke {
public String name;
public int capacity;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
}
下面是測(cè)試類:
public class JsonTest {
@Test
public void JsonTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
String jsonStr = " {\n" +
" \"name\":\"Coke\",\n" +
" \"capacity\":500\n" +
"}";
//json deserialization
Coke coke = mapper.readValue(jsonStr,Coke.class);
System.out.println(coke.capacity);
//json serialization
Coke coke1 = new Coke();
coke1.setName("BigCoke");
coke1.setCapacity(680);
String serializationJson = mapper.writeValueAsString(coke1);
System.out.println(serializationJson);
}
}
輸出結(jié)果:

對(duì)單個(gè)類的序列化和反序列化,只要不是結(jié)構(gòu)過(guò)于復(fù)雜,其操作還是比較簡(jiǎn)單的。對(duì)于此類型的序列化和反序列在這里我就不贅述了。
我們現(xiàn)在要討論的情況是,假如我們現(xiàn)在要對(duì)json對(duì)象進(jìn)行反序列化操作,但是我們并不知知道具體的json格式,也就是說(shuō)我們不知道json有哪些字段。這在實(shí)際生活中很常見(jiàn),比如在商店中,每件商品都有不同的屬性。飲料會(huì)有容量屬性,而馬桶,我們一般不會(huì)去考慮"容量"這種東西吧。那我們又該如何去做這種可能性很多的反序列化呢?
問(wèn)題:我們可以做反序列化,但是我們得知道這個(gè)json文件或者字符串對(duì)應(yīng)的類,而上述的情況沒(méi)法做到"運(yùn)行前"就知道是什么商品。只有在用戶付款時(shí)(運(yùn)行時(shí)),我們才知道這個(gè)商品是什么。
分析:我們沒(méi)法在運(yùn)行前知道需要反序列化的
商品是什么,但是我們知道一共有哪些商品可以被反序列化。而反序列化所需要的類我們也可以在工程中根據(jù)商品類型直接定義。我們要做的只是在獲取到商品時(shí)告訴它需要反序列化成哪個(gè)對(duì)象就OK了。而商品類型,我們可以根據(jù)商品名來(lái)判斷。那我們現(xiàn)在需要的就是一種可以根據(jù)json文件或json字符串中某個(gè)字段判斷出需要反序列化成哪一種對(duì)象的方法。幸運(yùn)的是,jackson也提供了解決這類問(wèn)題的方案。
2. 多態(tài)類型的處理
Jackson支持多態(tài)類型配置,在進(jìn)行jackson反序列化時(shí),可以根據(jù)配置轉(zhuǎn)換成相應(yīng)的子類對(duì)象。
其配置主要時(shí)通過(guò)相關(guān)的注解實(shí)現(xiàn)的。
@JsonTypeInfo
查看注解定義,其結(jié)構(gòu)如圖:

由上圖可以看出,這個(gè)注解一共有4個(gè)字段,分別是
use,include,property和defaultImpl。下面分別對(duì)這4個(gè)字段進(jìn)行說(shuō)明。
-
Id類型的use
這個(gè)字段時(shí)用來(lái)指定根據(jù)哪種類型的元數(shù)據(jù)來(lái)進(jìn)行序列化和反序列化??蛇x的值有:- JsonTypeInfo.Id.CLASS
- sonTypeInfo.Id.MINIMAL_CLASS
- JsonTypeInfo.Id.NAME
- JsonTypeInfo.Id.CUSTOM
- JsonTypeInfo.Id.NONE
這里我們選擇的是JsonTypeInfo.Id.NAME這個(gè)值,它表示的是我們的Serde將會(huì)使用字段名稱作為依據(jù)。針對(duì)上述場(chǎng)景,我們將會(huì)根據(jù)商品名稱來(lái)進(jìn)行serde。
-
As類型的include
這個(gè)字段是用來(lái)指定我們的元信息是如何被包含進(jìn)去的,可選的值如下:- JsonTypeInfo.As.PROPERTY
- JsonTypeInfo.As.EXISTING_PROPERTY
- JsonTypeInfo.As.EXTERNAL_PROPERTY
- JsonTypeInfo.As.WRAPPER_OBJECT
- JsonTypeInfo.As.WRAPPER_ARRAY
這個(gè)字段我們選擇的是JsonTypeInfo.As.PROPERTY,它所表示的意思是包含機(jī)制將會(huì)使用一個(gè)具體的屬性值。
String類型的property
只用當(dāng)use為JsonTypeInfo.Id.CUSTOM,或者include為JsonTypeInfo.As.PROPERTY時(shí)才會(huì)配置這個(gè)值。這個(gè)就可以用來(lái)表示具體的依據(jù)字段。
下面是該注解的使用 :
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY,property = "productName")
這個(gè)表示的就是在進(jìn)行反序列化時(shí),我們依據(jù)productName這個(gè)字段來(lái)區(qū)分需要轉(zhuǎn)換成的對(duì)象。例如,productName="Coke"時(shí),我們就將json反序列化成Coke對(duì)象。
@JsonSubTypes
這個(gè)注解是結(jié)合上個(gè)注解一起完成多態(tài)反序列化的。上個(gè)注解指定了反序列化的標(biāo)標(biāo)識(shí),而這個(gè)注解指定了每個(gè)標(biāo)識(shí)對(duì)應(yīng)的子類。
注解的結(jié)構(gòu)如下:

由注解的結(jié)構(gòu)圖可以看出,這注解只有一個(gè)字段就是
@Type類型的數(shù)組。而@Type的value就是子類,name即為子類對(duì)應(yīng)的標(biāo)識(shí)。下面是該注解的使用:
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = ClassA.class, name = "A"),
@JsonSubTypes.Type(value = ClassA.class, name = "B")
})
上面代碼所做的工作就是,當(dāng)檢測(cè)標(biāo)識(shí)為“A”時(shí)就將其反序列化ClassA,為“B”時(shí)就反序列化成ClassB。
既然已經(jīng)知道兩個(gè)注解的用法了,接下來(lái)我們就通過(guò)一個(gè)Demo看看他們?cè)谖覀兊拇a中該如何發(fā)揮作用。
3. Demo
場(chǎng)景描述:近日,某游戲廠家出品一種新的游戲裝備實(shí)體卡。玩家購(gòu)買實(shí)體卡通過(guò)掃碼之后就可以獲得相應(yīng)的道具,這些卡機(jī)具收藏價(jià)值。而每張卡的道具都是通過(guò)json來(lái)描述的,當(dāng)玩家掃描后,后臺(tái)就會(huì)根據(jù)這些描述信息把裝備卡轉(zhuǎn)換成相應(yīng)的道具。目前已出的裝備卡有三種,星空魔杖,代達(dá)羅斯之殤和巨大瓶飲料。三個(gè)裝備的描述信息分別如下:
- 星空魔杖
{
"name":"Star wand" ,
"length":35,
"price":120,
"effect":["getting greater", "getting handsome","getting rich"]
}
- 代達(dá)羅斯之殤
{
"name":"Daedalus",
"weight":"5kg",
"damage":1200,
"roles":["assassinator","soldier"],
"origin":{
"name":"Mainland of warcraft",
"date":"142-12-25"
}
}
- 巨大瓶飲料
{
"name":"Huge drink",
"capacity":500000,
"effect":"quenching your thirst and tasting good"
}
首先定義父類,用于反序列化時(shí)指定參數(shù)。
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,property = "name")
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = Daedalus.class, name = "Daedalus"),
@JsonSubTypes.Type(value = HugeDrink.class, name = "Huge drink"),
@JsonSubTypes.Type(value = StarWand.class, name = "Star wand"),
})
public interface Equipment {
}
這個(gè)接口的定義很簡(jiǎn)單,只是為了將各種裝備劃分成一類。然后通過(guò)注解指定了其子類類型,子類的標(biāo)識(shí)字段以及每個(gè)子類對(duì)應(yīng)的標(biāo)識(shí)值。
然后根據(jù)描述信息我們可以很輕松地寫(xiě)出這三個(gè)類的定義:
public class StarWand implements Equipment{
private String name;
private int length;
private int price;
private List<String> effect;
public void setName(String name) {
this.name = name;
}
public void setLength(int length) {
this.length = length;
}
public void setPrice(int price) {
this.price = price;
}
public void setEffect(List<String> effect) {
this.effect = effect;
}
public String getName() {
return name;
}
public int getLength() {
return length;
}
public int getPrice() {
return price;
}
public List<String> getEffect() {
return effect;
}
}
public class Daedalus implements Equipment {
private String name;
private String weight;
private int damage;
private List<String> roles;
private Map<String,String> origin;
public void setName(String name) {
this.name = name;
}
public void setWeight(String weight) {
this.weight = weight;
}
public void setDamage(int damage) {
this.damage = damage;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
public void setOrigin(Map<String, String> origin) {
this.origin = origin;
}
public String getName() {
return name;
}
public String getWeight() {
return weight;
}
public int getDamage() {
return damage;
}
public List<String> getRoles() {
return roles;
}
public Map<String, String> getOrigin() {
return origin;
}
}
public class HugeDrink implements Equipment{
private String name;
private int capacity;
private String effect;
public void setName(String name) {
this.name = name;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public void setEffect(String effect) {
this.effect = effect;
}
public String getName() {
return name;
}
public int getCapacity() {
return capacity;
}
public String getEffect() {
return effect;
}
}
最后是主方法
public class Main {
public static void main(String[] args) throws IOException {
String starWandStr = "{\n" +
" \"name\":\"Star wand\" ,\n" +
" \"length\":35,\n" +
" \"price\":120,\n" +
" \"effect\":[\"getting greater\", \"getting handsome\",\"getting rich\"]\n" +
"}";
String daedalusStr = "{\n" +
" \"name\":\"Daedalus\",\n" +
" \"weight\":\"5kg\",\n" +
" \"damage\":1200,\n" +
" \"roles\":[\"assassinator\",\"soldier\"],\n" +
" \"origin\":{\n" +
" \"name\":\"Mainland of warcraft\",\n" +
" \"date\":\"142-12-25\"\n" +
" }\n" +
"}";
String hugeDrinkStr = "{\n" +
" \"name\":\"Huge drink\",\n" +
" \"capacity\":500000,\n" +
" \"effect\":\"quenching your thirst and tasting good\"\n" +
"}";
ObjectMapper mapper = new ObjectMapper();
StarWand starWand = (StarWand)mapper.readValue(starWandStr, Equipment.class);
Daedalus daedalus = (Daedalus)mapper.readValue(daedalusStr, Equipment.class);
HugeDrink hugeDrink = (HugeDrink)mapper.readValue(hugeDrinkStr, Equipment.class);
System.out.println("大佬!您已獲得星空魔杖!屬性增幅:"+ starWand.getEffect().toString()+"!");
System.out.println("大佬!您已獲得代達(dá)羅斯之殤,增加了 " + daedalus.getDamage() + " 點(diǎn)輸出!");
System.out.println("大佬!您已獲得代達(dá)巨大瓶飲料,it "+ hugeDrink.getEffect()+"!");
}
}
控制臺(tái)輸出結(jié)果如下:

后記
首先需要注意的是,在做json反序列化時(shí),javaBean可以定義getter方法,但是setter方法必須定義。
再有就是當(dāng)我們有多個(gè)子類的時(shí)候,在基類上的注解就會(huì)顯的很長(zhǎng)。我們也有其他的方式可以實(shí)現(xiàn)。ObjectMapper類提供了一個(gè)registerSubtypes,通過(guò)這個(gè)方法我們可以直接注冊(cè)子類,就是說(shuō)我們不需要在定義基類的時(shí)候使用JsonSubTypes這個(gè)注解了。
mapper.registerSubtypes(new NamedType(HugeDrink.class, "Huge drink"));
mapper.registerSubtypes(new NamedType(Daedalus.class, "Daedalus"));
mapper.registerSubtypes(new NamedType(StarWand.class, "Star wand"));
上面的這中寫(xiě)法可以達(dá)到與JsonSubTypes注解相同的效果。
Demo地址:https://github.com/BigRantLing/JsonSerde