Jackson容许配置多态类型处理,当JSON面对的转换对象是一个接口、抽象类或者一个基类的时候,能够经过必定配置实现JSON的转换。在实际项目中,Controller层接收入参以及在Dao层将对象以json的形式存入数据库时均可能会遇到这个问题。而Springboot和mp都支持使用Jackson处理json,从而能够利用Jackson的特色,解决这一问题。java
注意
spring
为了代码简洁,这里的代码忽略了set和get方法和构造函数数据库
在本例中,父类Zoo有两个子类Dog和Cat类json
public static class Zoo { private String name; private AnimalTypeEnum animalType; }
父类Zoo中,包含一个表明动物种类的枚举字段数组
public enum AnimalTypeEnum { DOG("dog"), CAT("cat"); private final String name; }
对于子类Dog包含一个速度属性springboot
public static class Dog extends Zoo { private Double speed; }
对于子类Cat包含一个尺寸属性app
public static class Cat extends Zoo { private Integer size; }
咱们想作的事情是根据Zoo中的动物类型枚举字段animalType,将JSON反序列化为两种子类框架
使用Jackson提供的处理注解能够实现上述功能ide
@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()); }
能够看到,通过添加注解能够实现咱们的需求
这样无论是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; }