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

  1. 方案二的实现
    1.1 @JsonTypeInfo注解
    1.2 @JsonSubTypes注解
    1.3 @JsonTypeName注解
  2. 智能版:扩展@JsonTypeIdResolver的使用
    3.1 自定义TypeIdResolver
    3.2 实现方案
  3. 3.2.1 定义解析器
  4. 3.2.2 使用
  5. 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)

  6. use:定义使用哪一种类型识别码,它有下面几个可选值:
  7. 此属性只有当:

  8. use为JsonTypeInfo.Id.CLASS(若不指定property则默认为@class)、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property则默认为@c)、JsonTypeInfo.Id.NAME(若不指定property默认为@type),
  9. include为JsonTypeInfo.As.PROPERTY、JsonTypeInfo.As.EXISTING_PROPERTY、JsonTypeInfo.As.EXTERNAL_PROPERTY
  10. 时才有效。

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

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

  13. 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

  14. idFromValueAndType:是序列化的时候告诉序列化器怎么生成标识符。
  15. typeFromId:是反序列化的时候告诉序列化器怎么根据标识符来识别到具体类型,这里用了反射,在程序启动时,把要加载的包通过Reflections加载进来。
  16. 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);
            System.out.println(s);
            animalDto = objectMapper.readValue(s, AnimalDto.class);
            System.out.println(animalDto);