美文网首页JSONalready
Jackson父子类多态处理(注解实现)

Jackson父子类多态处理(注解实现)

作者: 小胖学编程 | 来源:发表于2022-03-09 21:36 被阅读0次
  1. 方案二的实现
    1.1 @JsonTypeInfo注解
    1.2 @JsonSubTypes注解
    1.3 @JsonTypeName注解
  2. 案例
  3. 智能版:扩展@JsonTypeIdResolver的使用
    3.1 自定义TypeIdResolver
    3.2 实现方案
    • 3.2.1 定义解析器
    • 3.2.2 使用
    • 3.2.3 测试
      推荐阅读

在Controller定义了入参后,随着业务的扩展,需要业务的扩展,需要不断的为入参新增字段,但是不同的业务需要使用不同的字段。若一直向入参中新增字段(不满足开闭原则)就会导致后期的不可维护性。

方案一:Controller层接收的是String类型,然后通过手动的方式来进行反序列化为子类对象。

方案二:使用Jackson的多态处理。

1. 方案二的实现

使用方式:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include =  JsonTypeInfo.As.EXISTING_PROPERTY ,property = "contentType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = RoleUser.class, name = "1"),
        @JsonSubTypes.Type(value = TokenUser.class, name = "2"),
})
@Data
public class AbstractBaseEntity {
    private String contentType;
    private String userName;
    private String password;
}

jackson允许配置多态处理,当进行反序列化时,JSON数据匹配的对象可能有多个子类型,为了正确的读取对象的类型,我们需要添加一些类型信息。可以通过下面几个注解来实现:

1.1 @JsonTypeInfo注解

作用在接口/类上,被用来开启多态类型的处理,对基类/接口和子类/实现类都有效。

样例:@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include = JsonTypeInfo.As.EXISTING_PROPERTY ,property = "contentType", visible = true)

  • use:定义使用哪一种类型识别码,它有下面几个可选值:
枚举值 作用
JsonTypeInfo.Id.CLASS 使用完全限定类名做识别
JsonTypeInfo.Id.MINIMAL_CLASS 若基类和子类在同一包类,使用类名(忽略包名)作为识别码
JsonTypeInfo.Id.NAME 一个合乎逻辑的指定名称
JsonTypeInfo.Id.CUSTOM 自定义识别码,由@JsonTypeIdResolver对应
JsonTypeInfo.Id.NONE 不使用识别码
  • include(可选):指定识别码是如何被包含进去的,它有下面几个可选值:
枚举值 作用
JsonTypeInfo.As.PROPERTY 作为数据的兄弟属性
JsonTypeInfo.As.EXISTING_PROPERTY 作为POJO中已经存在的属性,需要手动set
JsonTypeInfo.As.EXTERNAL_PROPERTY 作为扩展属性
JsonTypeInfo.As.WRAPPER_OBJECT 作为一个包装的对象
JsonTypeInfo.As.WRAPPER_ARRAY 作为一个包装的数组
  • property(可选):制定识别码的属性名称:

此属性只有当:

  1. use为JsonTypeInfo.Id.CLASS(若不指定property则默认为@class)、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property则默认为@c)、JsonTypeInfo.Id.NAME(若不指定property默认为@type),
  2. include为JsonTypeInfo.As.PROPERTY、JsonTypeInfo.As.EXISTING_PROPERTY、JsonTypeInfo.As.EXTERNAL_PROPERTY

时才有效。

  • defaultImpl(可选):如果类型识别码不存在或者无效,可以使用该属性来制定反序列化时使用的默认类型。

  • visible(可选,默认为false):是否可见
    属性定义了类型标识符的值是否会通过JSON流成为反序列化器的一部分,默认为fale,也就是说,jackson会从JSON内容中处理和删除类型标识符再传递给JsonDeserializer。

1.2 @JsonSubTypes注解

作用于类/接口,用来列出给定类的子类,只有当子类类型无法被检测到时才会使用它,一般是配合@JsonTypeInfo在基类上使用,比如:

@JsonSubTypes的值是一个@JsonSubTypes.Type[]数组,里面枚举了多态类型(value对应子类)和类型的标识符值(name对应@JsonTypeInfo中的property标识名称的值,此为可选值。若不制定需由@JsonTypeName在子类上制定)

1.3 @JsonTypeName注解

作用于子类,用来为多态子类指定类型标识符的值

@JsonTypeInfo(use = Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "contentType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = RoleUser.class),
        @JsonSubTypes.Type(value = TokenUser.class),
})
@Data
public class AbstractBaseEntity

+

@Data
@JsonTypeName("1")
public class RoleUser extends AbstractBaseEntity

等效于:

@JsonTypeInfo(use = Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "contentType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = RoleUser.class, name = "1"),
        @JsonSubTypes.Type(value = TokenUser.class, name = "2"),
})
@Data
public class AbstractBaseEntity

2. 案例

基类的使用:

@JsonTypeInfo(use = Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "contentType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = RoleUser.class, name = "1"),
        @JsonSubTypes.Type(value = TokenUser.class, name = "2"),
})
@Data
public class AbstractBaseEntity {
    /**
     * 1. 系列化时 @JsonTypeInfo 使用的是EXISTING_PROPERTY策略(已经存在的字段),故需要手动的填充这个字段。
     * 2. 在反序列化对象时,@JsonSubTypes.Type根据name属性的不同,来转化为不同的子类对象
     */
    private String contentType;
    private String userName;
    private String password;
}

子类的创建:

@Data
public class RoleUser extends AbstractBaseEntity{

    private String roleName;

    @Override
    public String toString() {
        return "RoleUser{" +
                "abstractBaseEntity='" + super.toString() + '\'' +
                "roleName='" + roleName + '\'' +
                '}';
    }
}
@Data
public class TokenUser extends AbstractBaseEntity {

    private String token;


    @Override
    public String toString() {
        return "TokenUser{" +
                "abstractBaseEntity='" + super.toString() + '\'' +
                "token='" + token + '\'' +
                '}';
    }
}

测试代码:

public class Test {

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        
        RoleUser roleUser = new RoleUser();
        roleUser.setRoleName("role-1");
        roleUser.setPassword("rolePwd");
        roleUser.setUserName("roleUserName");
        roleUser.setContentType("1");   

        TokenUser tokenUser = new TokenUser();
        tokenUser.setToken("token-1");
        tokenUser.setPassword("tokenPassword");
        tokenUser.setUserName("tokenUserName");
        tokenUser.setContentType("2");
        //序列化
        String roleStr = objectMapper.writeValueAsString(roleUser);
        String tokenStr = objectMapper.writeValueAsString(tokenUser);
        System.out.println(roleStr);
        System.out.println(tokenStr);
        //反序列化
        AbstractBaseEntity roleEntity = objectMapper.readValue(roleStr, AbstractBaseEntity.class);
        AbstractBaseEntity tokenEntity = objectMapper.readValue(tokenStr, AbstractBaseEntity.class);
        System.out.println(roleEntity);
        System.out.println(tokenEntity);
    }
}

Spring上的测试代码:

@Slf4j
@RestController
public class JacksonController {
    @RequestMapping("/test/js")
    @ResponseBody
    public String testEnvV3(@RequestBody AbstractBaseEntity entity) {
        RoleUser roleUser = null;
        if (entity.getContentType().equals("1")) {
            roleUser = (RoleUser) entity;
        }
        return JSON.toJSONString(roleUser);
    }
}

3. 智能版:扩展@JsonTypeIdResolver的使用

Jackson 多态序列化可以通过@JsonSubtypes来实现,但总觉得不是很方便,比如新增子类的时候都要去加一下JsonSubTypes。

有没有一个比较智能化的扩展?

有的@JsonTypeInfo使用JsonTypeInfo.Id.CUSTOM策略,然后自定义解析规则。

@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type")
@JsonTypeIdResolver(JacksonTypeIdResolver.class)

3.1 自定义TypeIdResolver

  • idFromValueAndType:是序列化的时候告诉序列化器怎么生成标识符。
  • typeFromId:是反序列化的时候告诉序列化器怎么根据标识符来识别到具体类型,这里用了反射,在程序启动时,把要加载的包通过Reflections加载进来。

3.2 实现方案

3.2.1 定义解析器

public class JacksonTypeIdResolver implements TypeIdResolver {
    private JavaType baseType;

    @Override
    public void init(JavaType javaType) {
        baseType = javaType;
    }


    @Override
    public String idFromValue(Object o) {
        return idFromValueAndType(o, o.getClass());
    }


    /**
     * 序列化时填充什么对象
     */
    @Override
    public String idFromValueAndType(Object o, Class<?> aClass) {
        //有出现同名类时可以用这个来做区别
        JsonTypeName annotation = aClass.getAnnotation(JsonTypeName.class);
        if (annotation != null) {
            return annotation.value();
        }
        String name = aClass.getName();
        String[] splits = StringUtils.split(name, ".");
        String className = splits[splits.length - 1];
        return className;
    }


    /**
     * idFromValueAndType 是序列化的时候告诉序列化器怎么生成标识符
     * <p>
     * typeFromId是反序列化的时候告诉序列化器怎么根据标识符来识别到具体类型,这里用了反射,在程序启动时,把要加载的包通过Reflections加载进来
     */
    @Override
    public JavaType typeFromId(DatabindContext databindContext, String type) {
        Class<?> clazz = getSubType(type);
        if (clazz == null) {
            throw new IllegalStateException("cannot find class '" + type + "'");
        }
        return databindContext.constructSpecializedType(baseType, clazz);
    }

    public Class<?> getSubType(String type) {
        Reflections reflections = ReflectionsCache.getReflections();
        Set<Class<?>> subTypes = reflections.getSubTypesOf((Class<Object>) baseType.getRawClass());
        for (Class<?> subType : subTypes) {
            JsonTypeName annotation = subType.getAnnotation(JsonTypeName.class);
            if (annotation != null && annotation.value().equals(type)) {
                return subType;
            } else if (subType.getSimpleName().equals(type) || subType.getName().equals(type)) {
                return subType;
            }
        }
        return null;
    }

    @Override
    public String idFromBaseType() {
        return idFromValueAndType(null, baseType.getClass());
    }

    @Override
    public String getDescForKnownTypeIds() {
        return null;
    }

    @Override
    public JsonTypeInfo.Id getMechanism() {
        return JsonTypeInfo.Id.CUSTOM;

    }
}

反射相关,引入依赖:

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>
public class ReflectionsCache {
    private static Reflections reflections;

    public static void setReflections(Reflections _reflections) {
        reflections = _reflections;
    }

    public static Reflections getReflections() {
        return reflections;
    }

}
@Service
public class ClassCacheInitializing implements InitializingBean {

    private String packages="com.tellme.jackson";

    /**
     * 反射会有点耗时,所以程序启动的时候加载完放到缓存里面去,后面要用的时候直接去缓存取
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        Reflections reflections = new Reflections(packages);
        if (reflections != null) {
            ReflectionsCache.setReflections(reflections);
        }
    }

3.2.2 使用

基类:

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type")
@JsonTypeIdResolver(JacksonTypeIdResolver.class)
public class Animal {
    private String name;
}

子类(子类上无需在使用注解):

@Data
public class Dog extends Animal {
    private int age;

    @Override
    public String toString() {
        return "Dog{" +
                "animal=" + super.toString() +
                "age=" + age +
                '}';
    }
}

3.2.3 测试

public class TestEx {

    public static void main(String[] args) throws Exception {
        ClassCacheInitializing classCacheInitializing=new ClassCacheInitializing();
        classCacheInitializing.afterPropertiesSet();

        ObjectMapper objectMapper = new ObjectMapper();
        AnimalDto animalDto = new AnimalDto();
        Dog dog = new Dog();
        dog.setName("dog");
        dog.setAge(1);
        animalDto.setAnimal(dog);
        String s = objectMapper.writeValueAsString(animalDto);
        System.out.println(s);
        animalDto = objectMapper.readValue(s, AnimalDto.class);

        System.out.println(animalDto);
    }
}

推荐阅读

Jackson TypeIdResolver实现多态序列化和反序列化

Java非常好用的反射框架Reflections

相关文章

  • Jackson父子类多态处理(注解实现)

    方案二的实现1.1 @JsonTypeInfo注解1.2 @JsonSubTypes注解1.3 @JsonType...

  • Java多态

    1.基于继承 覆盖子类覆盖父类的方法,实现多态 抽象子类实现父类的抽象方法,实现多态abstract 不能与pri...

  • [Java 编程思想]第八章 多态

    多态: 多态实现的三大要素: 要有继承关系 子类要重写父类的方法 父类引用指向子类对。eg:class Peopl...

  • iOS-Swift-多态实现原理、初始化器

    一. 多态实现原理 多态就是父类指针指向子类对象关于多态:在编译的时候并不知道要调用的是父类还是子类的方法,运行的...

  • 面向对象三大特性(三):多态

    多态是对继承的扩展机制,指的是父类的引用指向子类的对象。多态实现的前提条件是: 有继承关系 子类重写了父类方法 父...

  • dart快速入门教程 (7.4)

    7.12.多态 多态字面上理解就是多种状态,通俗的说,多态表现为父类定义一个方法不去实现,子类继承这个方法后实现父...

  • 运行时多态

    运行时多态 多态:子类在父类统一行为接口下,表现不同的实现方式 对比重写与重载 子类重写父类同名同参数方法:子类只...

  • 深入理解 Python 类和对象(1) - 鸭子类型和多态

    什么是鸭子类型? Java 中实现多态,需要继承父类,并覆盖父类中的方法。 Python 中实现多态,不需要继承任...

  • 多态

    一、多态 1、引用多态父类的引用指向本类的对象父类的引用指向子类的对象 继承是实现多态的基础 2、方法多态创建本类...

  • Swift 多态实现探究

    多态 父类指针指向子类对象 Swift 中多态的实现类似于 c++ 中的虚表 OC 多态实现利用的是 Runtim...

网友评论

    本文标题:Jackson父子类多态处理(注解实现)

    本文链接:https://www.haomeiwen.com/subject/awztdrtx.html