在Springboot + Mybaitis-plus 项目中利用Jackson实现json对java多态的(反)序列化器

Jackson允许配置多态类型处理,当JSON面对的转换对象是一个接口、抽象类或者一个基类的时候,可以通过一定配置实现JSON的转换。在实际项目中,Controller层接收入参以及在Dao层将对象以json的形式存入数据库时都可能会遇到这个问题。而Springboot和mp都支持使用Jackson处理json,从而可以利用Jackson的特点,解决这一问题。

注意

为了代码简洁,这里的代码忽略了set和get方法和构造函数

在本例中,父类Zoo有两个子类Dog和Cat类

public static class Zoo {
  
    private String name;
    private AnimalTypeEnum animalType;
  
}

父类Zoo中,包含一个代表动物种类的枚举字段

public enum AnimalTypeEnum {
    DOG("dog"),
    CAT("cat");
    private final String name;
}

对于子类Dog包含一个速度属性

public static class Dog extends Zoo {
    private Double speed;
}

对于子类Cat包含一个尺寸属性

public static class Cat extends Zoo {
    private Integer size;
}

我们想做的事情是根据Zoo中的动物类型枚举字段animalType,将JSON反序列化为两种子类

方法一

使用Jackson提供的处理注解可以实现上述功能

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "animalType",
        visible = true
)
@JsonSubTypes(
        {
                @JsonSubTypes.Type(value = Dog.class, name = "DOG"),
                @JsonSubTypes.Type(value = Cat.class, name = "CAT")
        }
)
public static class Zoo {
  
    private String name;
    private AnimalTypeEnum animalType;
  
}

@JsonTypeInfo()

该注解表示对该类开启多态类型处理,包含四个属性

use 代表使用哪一种类型识别码

JsonTypeInfo.Id.NAME 是本例中选择的类型识别码,意指一个指定的名字

include代表识别码是如何包含进JSON

JsonTypeInfo.As.EXISTING_PROPERTY 代表POJO中存在的类型
property 指定类型表示码的属性名称

“animalType” 就是POJO中的类型字段名
visible 代表类型标识符是否会进入反序列化,默认false

由于这里我们同样需要该字段反序列化,所以设置为true

@JsonSubTypes()

该注解用于给出给定类的子类

@JsonSubTypes.Type[]数组中给出了多态类和property中指定属性某个值之间的绑定关系。在上例中,Dog类和animalType = DOG的值进行了绑定

在父类Zoo上加入如上注解之后,即可实现多态反序列化

对应测试

public void method1Test() throws JsonProcessingException {

    ObjectMapper objectMapper = new ObjectMapper();
    Cat cat = new Cat("小猫", AnimalTypeEnum.CAT, 20);
    Dog dog = new Dog("小狗", AnimalTypeEnum.DOG, 30.03);

    String catJson = objectMapper.writeValueAsString(cat);
    String dogJson = objectMapper.writeValueAsString(dog);

    log.debug(catJson);
    log.debug(dogJson);

    //反序列化
    Zoo catZoo = objectMapper.readValue(catJson, Zoo.class);
    Zoo dogZoo = objectMapper.readValue(dogJson, Zoo.class);

    //类型一致
    assertEquals(catZoo.getClass(),cat.getClass());
    assertEquals(dogZoo.getClass(),dog.getClass());

    //参数值一致
    assertEquals(20,((Cat)catZoo).getSize());
    assertEquals(30.03,((Dog)dogZoo).getSpeed());

}

image

可以看到,经过添加注解可以实现我们的需求

这样不管是springboot还是mybaitis-plus进行反序列化的时候,都通过注解的信息按照我们的要求进行反序列化

方法二

在项目中,一个基类会有很多的子类,并且随着项目的深入,子类可能会越来越多。使用上面的方法,需要不停的添加@JsonSubTypes中的内容,十分繁琐。这种写法是 **违反开闭原则(OCP)**的。

通过阅读源码,我们可以看到,其多态处理的基本原理就是将子类何其对应的名称之间的绑定关系注册到ObjectMapper中。

方法二的思路是给每个子类增加一个注解@JsonTypeName(value = “”),然后通过扫描所有带有注解的类,将所有带有标记的类注册到ObjectMapper中。

在Springboot中自定义ObjectMapper有很多办法,可以参考在SpringBoot中自定义 Jackson的ObjectMapper

首先生成一个ObjectMapper 的bean

@Configuration
public class ObjectMapperConfig {
    @Bean
    @Primary
    //使这个bean优先被注入
    public ObjectMapper objectMapper() {

        ObjectMapper objectMapper = new ObjectMapper();
      
        //使用reflection框架,获取本包下的所有带有@JsonTypeName的注解
        Reflections reflections = new Reflections("cn.");
        Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(JsonTypeName.class);
        //将这个上面扫描得到的类注册进这个ObjectMapper中
        objectMapper.registerSubtypes(classSet);
      
        //这里是将我们定义好的objectMapper set 进 Mybaitis-plus的Jackson处理器中,从而使得MP也可以				顺利的进行反序列化
        JacksonTypeHandler.setObjectMapper(objectMapper);
        return objectMapper;
    }
}

父类只需要添加这样一个注解

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "animalType",
        visible = true
)
public static class Zoo {
  
    private String name;
    private AnimalTypeEnum animalType;
  
}

子类添加注解

@JsonTypeName("DOG")
public static class Dog extends Zoo {
    private Double speed;
}

方法三

在我们的场景中,分类标识符是一个枚举类型。因此,我们希望将所有的子类和标识符名称对应的信息全部放在该枚举类中,使得仅通过枚举类就可以绑定好所有子类和名称之间的关系。

定义一个接口和注解,并在接口上使用了这个注解

@JsonSubTypeEnum.JsonSubTypeAnnotation
public interface JsonSubTypeEnum {
 
    Class<?> getJsonType();

    @Documented
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface JsonSubTypeAnnotation {
    }
}

这个接口定义了一个获取子类类信息的方法

public enum AnimalType implements JsonSubTypeEnum {
    DOG(Dog.class),
    CAT(Cat.class),
    ;
    private final Class<? extends Animal> animalClass;

    @Override
    public Class<?> getJsonType() {
        return this.animalClass;
    }
}

让需要用于分类的枚举实现这个接口,枚举中的animalClass属性,用来记录该标识符对应的子类的类别。

再来看ObjectMapper bean

@Bean
@Primary
public static ObjectMapper getObjectMapper(){
    ObjectMapper objectMapper = new ObjectMapper();
    Reflections reflections = new Reflections("com.");
  //获取所有带有自定义注解的类
    Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(JsonSubTypeEnum.JsonSubTypeAnnotation.class)//将其中的枚举类拿出来处理
      for (Class<?> enumTyp : classSet) {
        if (!enumTyp.isEnum()) {
            continue;
        }
        final Object[] enumConstants = enumTyp.getEnumConstants();
        for (Object e : enumConstants) {
            if (e instanceof JsonSubTypeEnum) {
                //将每个子类和标识符绑定注册进入objectMapper
                final Class<?> subType = ((JsonSubTypeEnum) e).getJsonType();
                final String name = ((Enum<?>) e).name();
                objectMapper.registerSubtypes(new NamedType(subType, name));
            }
        }
    }
         //这里是将我们定义好的objectMapper set 进 Mybaitis-plus的Jackson处理器中,从而使得MP也可以				顺利的进行反序列化
   JacksonTypeHandler.setObjectMapper(objectMapper);
   return objectMapper;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,我可以为您提供一个Java使用Jackson实现多态解析JSON的示例。这里是一个基本的示例代码: ``` public class Animal { public String name; public String type; } @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type" ) @JsonSubTypes({ @JsonSubTypes.Type(value = Dog.class, name = "dog"), @JsonSubTypes.Type(value = Cat.class, name = "cat"), }) public abstract class AnimalMixin { @JsonCreator public AnimalMixin(@JsonProperty("name") String name) { } } public class Dog extends Animal { public String breed; public Dog() {} public Dog(String name, String breed) { this.name = name; this.breed = breed; this.type = "dog"; } } public class Cat extends Animal { public int age; public Cat() {} public Cat(String name, int age) { this.name = name; this.age = age; this.type = "cat"; } } public static void main(String[] args) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); String json = "{ \"name\": \"Fido\", \"type\": \"dog\", \"breed\": \"beagle\" }"; Animal animal = mapper.readValue(json, Animal.class); System.out.println(animal.name); System.out.println(((Dog)animal).breed); } ``` 在这个示例,我们有一个Animal类,它有一个name属性和一个type属性。type属性用于标识动物的类型,它将在JSON解析期间用于决定将JSON转换为哪个子类。我们通过@JsonTypeInfo注释和@JsonSubTypes注释来指示Jackson使用多态解析JSON。@JsonTypeInfo注释告诉Jackson使用"type"属性来确定JSON应该被反序列化为哪个类。@JsonSubTypes注释告诉Jackson哪些子类需要被考虑。 我们还有一个AnimalMixin抽象类,它用于处理将JSON转换为适当的子类。在这个例子,我们有两个实现:Dog和Cat。两个类都有它们自己的属性,但都继承自Animal类。 在main方法,我们首先创建一个ObjectMapper对象,然后使用readValue方法将JSON字符串转换为Animal对象。然后我们打印了Animal对象的name属性和子类特有的属性。 这是一个Java使用Jackson实现多态解析JSON的基本示例。我希望它能帮助您。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值