相关文章推荐
大鼻子的大熊猫  ·  陈文斌教授即将做客第五期《成栋研习论坛》-东 ...·  3 月前    · 
坚韧的弓箭  ·  李幼斌,大器晚成《亮剑》李云龙《闯关东》朱开 ...·  1 年前    · 
绅士的酱牛肉  ·  步韵古人七律3首,诗词水平快速提高。尘客行过 ...·  2 年前    · 
高兴的山羊  ·  《金爆行动》影评| 惊喜结局让人大呼过瘾!·  2 年前    · 
潇洒的牛排  ·  小说推荐:文笔较好的总裁文(虐文篇) - 知乎·  2 年前    · 
Code  ›  Jackson父子类多态处理(注解实现)开发者社区
序列化 多态 string类
https://cloud.tencent.com/developer/article/1962417
焦虑的面包
2 年前
作者头像
用户7741497
0 篇文章

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

前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP
返回腾讯云官网
社区首页 > 专栏 > hml_知识记录 > Jackson父子类多态处理(注解实现)

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

作者头像
用户7741497
发布 于 2022-03-24 12:09:01
1K 0
发布 于 2022-03-24 12:09:01
举报
  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 是序列化的时候告诉序列化器怎么生成标识符
     * 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);
 
推荐文章
大鼻子的大熊猫  ·  陈文斌教授即将做客第五期《成栋研习论坛》-东北林业大学共青团在线
3 月前
坚韧的弓箭  ·  李幼斌,大器晚成《亮剑》李云龙《闯关东》朱开山成经典_哔哩哔哩_bilibili
1 年前
绅士的酱牛肉  ·  步韵古人七律3首,诗词水平快速提高。尘客行过,梦乡深处谢花开|李白|陈后主|范成大|李商隐_网易订阅
2 年前
高兴的山羊  ·  《金爆行动》影评| 惊喜结局让人大呼过瘾!
2 年前
潇洒的牛排  ·  小说推荐:文笔较好的总裁文(虐文篇) - 知乎
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号