JSONalready

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

2022-03-09  本文已影响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)

枚举值 作用
JsonTypeInfo.Id.CLASS 使用完全限定类名做识别
JsonTypeInfo.Id.MINIMAL_CLASS 若基类和子类在同一包类,使用类名(忽略包名)作为识别码
JsonTypeInfo.Id.NAME 一个合乎逻辑的指定名称
JsonTypeInfo.Id.CUSTOM 自定义识别码,由@JsonTypeIdResolver对应
JsonTypeInfo.Id.NONE 不使用识别码
枚举值 作用
JsonTypeInfo.As.PROPERTY 作为数据的兄弟属性
JsonTypeInfo.As.EXISTING_PROPERTY 作为POJO中已经存在的属性,需要手动set
JsonTypeInfo.As.EXTERNAL_PROPERTY 作为扩展属性
JsonTypeInfo.As.WRAPPER_OBJECT 作为一个包装的对象
JsonTypeInfo.As.WRAPPER_ARRAY 作为一个包装的数组

此属性只有当:

  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

时才有效。

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

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

上一篇 下一篇

猜你喜欢

热点阅读