【轉】Jackson之多態反序列化(父類轉不同子類)

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\":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);
    }
}

輸出結果:

對單個類的序列化和反序列化,只要不是結構過於複雜,其操作還是比較簡單的。對於此類型的序列化和反序列在這裏我就不贅述了。 我們現在要討論的情況是,假如我們現在要對json對象進行反序列化操作,但是我們並不知知道具體的json格式,也就是說我們不知道json有哪些字段。這在實際生活中很常見,比如在商店中,每件商品都有不同的屬性。飲料會有容量屬性,而馬桶,我們一般不會去考慮"容量"這種東西吧。那我們又該如何去做這種可能性很多的反序列化呢?
問題:我們可以做反序列化,但是我們得知道這個json文件或者字符串對應的類,而上述的情況沒法做到"運行前"就知道是什麼商品。只有在用戶付款時(運行時),我們才知道這個商品是什麼。 分析:我們沒法在運行前知道需要反序列化的商品是什麼,但是我們知道一共有哪些商品可以被反序列化。而反序列化所需要的類我們也可以在工程中根據商品類型直接定義。我們要做的只是在獲取到商品時告訴它需要反序列化成哪個對象就OK了。而商品類型,我們可以根據商品名來判斷。那我們現在需要的就是一種可以根據json文件或json字符串中某個字段判斷出需要反序列化成哪一種對象的方法。幸運的是,jackson也提供瞭解決這類問題的方案。

  1. 多態類型的處理
    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看看他們在我們的代碼中該如何發揮作用。

  1. 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

轉載:https://zhuanlan.zhihu.com/p/96108902

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章