1.場景描述
JSON作為一種輕量級的資料交換格式,其清晰和簡潔的結構能夠輕松地與Java對象産生映射關系。例如,一個Coke(可口可樂)類的java代碼如下:
public class Coke{
String name = "Coke";
int capacity= 500;
}
用json描述該類:
{
"name":"Coke",
"capacity":500
}
而這種映射關系可以通過代碼進行轉換,也就是所謂的json序列化和反序列化。
序列化:是指将Java對象轉換成Json檔案或者Json字元串;
反序列化:是指将Json檔案或者Json字元串轉換成Java對象。
Java代碼實作Json的序列化和反序列化并不難,尤其是現在的很多架構簡化了很多的過程。下面以我常用的
jackson
為例,實作簡單的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;
}
}
下面是測試類:
public class JsonTest {
@Test
public void JsonTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
String jsonStr = " {n" +
" "name":"Coke",n" +
" "capacity":500n" +
"}";
//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);
}
}
輸出結果:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CNhZWM4EDZ3YTOlBzMkFDZ1EGZ1MTMhdjMwQDM5MjZ28CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
對單個類的序列化和反序列化,隻要不是結構過于複雜,其操作還是比較簡單的。對于此類型的序列化和反序列在這裡我就不贅述了。 我們現在要讨論的情況是,假如我們現在要對json對象進行反序列化操作,但是我們并不知知道具體的json格式,也就是說我們不知道json有哪些字段。這在實際生活中很常見,比如在商店中,每件商品都有不同的屬性。飲料會有容量屬性,而馬桶,我們一般不會去考慮"容量"這種東西吧。那我們又該如何去做這種可能性很多的反序列化呢?
問題:我們可以做反序列化,但是我們得知道這個json檔案或者字元串對應的類,而上述的情況沒法做到"運作前"就知道是什麼商品。隻有在使用者付款時(運作時),我們才知道這個商品是什麼。
分析:我們沒法在運作前知道需要反序列化的
商品
是什麼,但是我們知道一共有哪些
商品
可以被反序列化。而反序列化所需要的類我們也可以在工程中根據商品類型直接定義。我們要做的隻是在擷取到
商品
時告訴它需要反序列化成哪個對象就OK了。而商品類型,我們可以根據商品名來判斷。
那我們現在需要的就是一種可以根據json檔案或json字元串中某個字段判斷出需要反序列化成哪一種對象的方法。幸運的是,jackson也提供了解決這類問題的方案。
2. 多态類型的處理
Jackson支援多态類型配置,在進行jackson反序列化時,可以根據配置轉換成相應的子類對象。
其配置主要時通過相關的注解實作的。
@JsonTypeInfo檢視注解定義,其結構如圖:
由上圖可以看出,這個注解一共有4個字段,分别是
use
,
include
,
property
和
defaultImpl
。下面分别對這4個字段進行說明。
*
Id
類型的
use
這個字段時用來指定根據哪種類型的中繼資料來進行序列化和反序列化。可選的值有:
1. JsonTypeInfo.Id.CLASS 2. sonTypeInfo.Id.MINIMAL_CLASS 3. JsonTypeInfo.Id.NAME 4. JsonTypeInfo.Id.CUSTOM 5. JsonTypeInfo.Id.NONE
這裡我們選擇的是
JsonTypeInfo.Id.NAME
這個值,它表示的是我們的Serde将會使用字段名稱作為依據。針對上述場景,我們将會根據商品名稱來進行serde。
-
類型的As
這個字段是用來指定我們的元資訊是如何被包含進去的,可選的值如下:include
- JsonTypeInfo.As.PROPERTY
- JsonTypeInfo.As.EXISTING_PROPERTY
- JsonTypeInfo.As.EXTERNAL_PROPERTY
- JsonTypeInfo.As.WRAPPER_OBJECT
-
JsonTypeInfo.As.WRAPPER_ARRAY
這個字段我們選擇的是JsonTypeInfo.As.PROPERTY,它所表示的意思是包含機制将會使用一個具體的屬性值。
-
類型的String
隻用當property
為use
,或者JsonTypeInfo.Id.CUSTOM
為include
JsonTypeInfo.As.PROPERTY
時才會配置這個值。這個就可以用來表示具體的依據字段。
下面是該注解的使用 :
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY,property = "productName")
這個表示的就是在進行反序列化時,我們依據
productName
這個字段來區分需要轉換成的對象。例如,
productName="Coke"
時,我們就将json反序列化成Coke對象。
@JsonSubTypes這個注解是結合上個注解一起完成多态反序列化的。上個注解指定了反序列化的标辨別,而這個注解指定了每個辨別對應的子類。
注解的結構如下:
由注解的結構圖可以看出,這注解隻有一個字段就是
@Type
類型的數組。而@Type的value就是子類,name即為子類對應的辨別。
下面是該注解的使用:
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = ClassA.class, name = "A"),
@JsonSubTypes.Type(value = ClassA.class, name = "B")
})
上面代碼所做的工作就是,當檢測辨別為“A”時就将其反序列化ClassA,為“B”時就反序列化成ClassB。
既然已經知道兩個注解的用法了,接下來我們就通過一個Demo看看他們在我們的代碼中該如何發揮作用。
3. Demo
場景描述:近日,某遊戲廠家出品一種新的遊戲裝備實體卡。玩家購買實體卡通過掃碼之後就可以獲得相應的道具,這些卡機具收藏價值。而每張卡的道具都是通過json來描述的,當玩家掃描後,背景就會根據這些描述資訊把裝備卡轉換成相應的道具。目前已出的裝備卡有三種,
星空魔杖
,
代達羅斯之殇
和
巨大瓶飲料
。三個裝備的描述資訊分别如下:
*
星空魔杖{
"name":"Star wand" ,
"length":35,
"price":120,
"effect":["getting greater", "getting handsome","getting rich"]
}
- 代達羅斯之殇
{
"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"
}
首先定義父類,用于反序列化時指定參數。
@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 {
}
這個接口的定義很簡單,隻是為了将各種裝備劃分成一類。然後通過注解指定了其子類類型,子類的辨別字段以及每個子類對應的辨別值。
然後根據描述資訊我們可以很輕松地寫出這三個類的定義:
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("大佬!您已獲得代達羅斯之殇,增加了 " + daedalus.getDamage() + " 點輸出!");
System.out.println("大佬!您已獲得代達巨大瓶飲料,it "+ hugeDrink.getEffect()+"!");
}
}
控制台輸出結果如下:
後記
首先需要注意的是,在做json反序列化時,javaBean可以定義getter方法,但是setter方法必須定義。
再有就是當我們有多個子類的時候,在基類上的注解就會顯的很長。我們也有其他的方式可以實作。ObjectMapper類提供了一個
registerSubtypes
,通過這個方法我們可以直接注冊子類,就是說我們不需要在定義基類的時候使用
JsonSubTypes
這個注解了。
mapper.registerSubtypes(new NamedType(HugeDrink.class, "Huge drink"));
mapper.registerSubtypes(new NamedType(Daedalus.class, "Daedalus"));
mapper.registerSubtypes(new NamedType(StarWand.class, "Star wand"));
上面的這中寫法可以達到與
JsonSubTypes
注解相同的效果。
Demo位址:https://github.com/BigRantLing/JsonSerde