Java

Jackson之多态反序列化

2019-06-12  本文已影响0人  RantLing

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);
    }
}

输出结果:

输出结果.png
对单个类的序列化和反序列化,只要不是结构过于复杂,其操作还是比较简单的。对于此类型的序列化和反序列在这里我就不赘述了。
我们现在要讨论的情况是,假如我们现在要对json对象进行反序列化操作,但是我们并不知知道具体的json格式,也就是说我们不知道json有哪些字段。这在实际生活中很常见,比如在商店中,每件商品都有不同的属性。饮料会有容量属性,而马桶,我们一般不会去考虑"容量"这种东西吧。那我们又该如何去做这种可能性很多的反序列化呢?
问题:我们可以做反序列化,但是我们得知道这个json文件或者字符串对应的类,而上述的情况没法做到"运行前"就知道是什么商品。只有在用户付款时(运行时),我们才知道这个商品是什么。
分析:我们没法在运行前知道需要反序列化的商品是什么,但是我们知道一共有哪些商品可以被反序列化。而反序列化所需要的类我们也可以在工程中根据商品类型直接定义。我们要做的只是在获取到商品时告诉它需要反序列化成哪个对象就OK了。而商品类型,我们可以根据商品名来判断。那我们现在需要的就是一种可以根据json文件或json字符串中某个字段判断出需要反序列化成哪一种对象的方法。幸运的是,jackson也提供了解决这类问题的方案。

2. 多态类型的处理

Jackson支持多态类型配置,在进行jackson反序列化时,可以根据配置转换成相应的子类对象。
其配置主要时通过相关的注解实现的。
@JsonTypeInfo
查看注解定义,其结构如图:

@JsonTypeInfo.png
由上图可以看出,这个注解一共有4个字段,分别是use,include,propertydefaultImpl。下面分别对这4个字段进行说明。
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY,property = "productName")

这个表示的就是在进行反序列化时,我们依据productName这个字段来区分需要转换成的对象。例如,productName="Coke"时,我们就将json反序列化成Coke对象。

@JsonSubTypes
这个注解是结合上个注解一起完成多态反序列化的。上个注解指定了反序列化的标标识,而这个注解指定了每个标识对应的子类。
注解的结构如下:

@JsonSubTypes.png
由注解的结构图可以看出,这注解只有一个字段就是@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()+"!");
    }
}

控制台输出结果如下:

image.png

后记

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

上一篇下一篇

猜你喜欢

热点阅读