Jackson之多態(tài)反序列化

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é)果:

輸出結(jié)果.png

對(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)如圖:

@JsonTypeInfo.png

由上圖可以看出,這個(gè)注解一共有4個(gè)字段,分別是use,include,propertydefaultImpl。下面分別對(duì)這4個(gè)字段進(jìn)行說(shuō)明。

  • Id類型的use
    這個(gè)字段時(shí)用來(lái)指定根據(jù)哪種類型的元數(shù)據(jù)來(lái)進(jìn)行序列化和反序列化??蛇x的值有:

    1. JsonTypeInfo.Id.CLASS
    2. sonTypeInfo.Id.MINIMAL_CLASS
    3. JsonTypeInfo.Id.NAME
    4. JsonTypeInfo.Id.CUSTOM
    5. 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)去的,可選的值如下:

    1. JsonTypeInfo.As.PROPERTY
    2. JsonTypeInfo.As.EXISTING_PROPERTY
    3. JsonTypeInfo.As.EXTERNAL_PROPERTY
    4. JsonTypeInfo.As.WRAPPER_OBJECT
    5. JsonTypeInfo.As.WRAPPER_ARRAY
      這個(gè)字段我們選擇的是JsonTypeInfo.As.PROPERTY,它所表示的意思是包含機(jī)制將會(huì)使用一個(gè)具體的屬性值。
  • String類型的property
    只用當(dāng)useJsonTypeInfo.Id.CUSTOM,或者includeJsonTypeInfo.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)如下:

@JsonSubTypes.png

由注解的結(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é)果如下:

image.png

后記

首先需要注意的是,在做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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容