Jackson多态处理

前言

博主github

博主个人博客http://blog.healerjean.com

Jackson允许配置多态类型处理,当进行反序列话时,Json数据匹配的对象可能有多个子类型,为了正确的读取对象的类型,我们需要添加一些类型信息。

1、解释

1.1、@JsonTypeInfo

@JsonTypeInfo这个注解可以直接放在类上,也可以放在某个属性上:下面是内部的属性值

1.1.1、use: (必选):

use: (必选):定义使用哪一种类型识别码(property为识别码的key),可选值有多种:在序列化时标志出不同的类型用什么区分,用在反序列化时转换成响应的类型

use属性值 若不指定property则默认 作用 是否依赖JsonTypeInfo的值
JsonTypeInfo.Id.NAME @type 使用JsonTypeInfo的值作为识别码的值 如果有多个子类的情况,必须有 @JsonSubTypes,否则无法判断是哪个子类
JsonTypeInfo.Id.CLASS @class 用类的全路劲名称来作为识别码的值 与是否有@JsonSubTypes无关
JsonTypeInfo.Id.MINIMAL_CLASS @c 表示具有最小路径的Java类名称用作识别 是否有@JsonSubTypes无关
JsonTypeInfo.Id.NONE 暂不介绍
JsonTypeInfo.Id.CUSTOM 暂不介绍

1.1.2、include (可选)

设置识别码包含在哪里。 包含类型元数据的一种机制

include属性值 作用
JsonTypeInfo.As.PROPERTY 作为POJO的属性出现 默认
JsonTypeInfo.As.WRAPPER_OBJECT 作为一个包装的对象
JsonTypeInfo.As.WRAPPER_ARRAY 作为一个包装的数组
JsonTypeInfo.As.EXTERNAL_PROPERTY 作为一个额外的属性,跟POJO同级,只能用于属性,如何作用于类则跟JsonTypeInfo.As.PROPERTY是相同效果
JsonTypeInfo.As.EXISTING_PROPERTY 序列化,则Jackson不主动处理,由我们自行处理。 反序列化的时候,跟JsonTypeInfo.As.PROPERTY的处理相同;

1.1.3、property 可选)

设置识别码是名称, 自定义的区分类型的id,根据 use的属性值不同,默认值不同,具体默认值看(1.1.1)

1.1.4、visible (可选)

visible(可选):定义识别码在反序列化时是否保留(不管false或true都不影响序列化)。默认是false,表示Jackson可以将识别码识别为类型后就删除。

1.2、@JsonSubTypes

可以用来表明这个父类的子类型有哪些

2、准备

2.1、Demo实体类

public abstract class Human {

    private String district;

    @Data
    public static class Man extends Human {
        private String manField;
    }

    @Data
    public static class Woman extends Human {
        private String womanField;
    }

}

2.2、正常情况下的错误的演示



    @Test
    public void normal() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Man man = new Man();
        man.setManField("男人");
        man.setDistrict("山西");
        String json = mapper.writeValueAsString(man);
        System.out.println(json);
        // {"district":"山西","manField":"男人"}
        //报错 子类转父类,再不能直接序列化为子类
        man =((Man)mapper.readValue(json, Human.class)) ;
    }
    

3、开始

3.1、实体demo

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})
public abstract class Human {

    private String district;

    @Data
    public static class Man extends Human {
        private String manField;
    }

    @Data
    public static class Woman extends Human {
        private String womanField;
    }

}

3.2、测试用例

  @Test
    public void testOne() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Man man = new Man();
        man.setManField("男人");
        man.setDistrict("北京");

        String manJson = mapper.writeValueAsString(man);
        log.info("序列化Man :【 {} 】", manJson);
        Human human = mapper.readValue(manJson, Human.class);
        log.info("子类转父类 ======================");
        String humanJson = mapper.writeValueAsString(human);
        log.info("反序列化man -> Human :【 {} 】", humanJson);
        log.info("human.getDistrict()  :【 {} 】",  human.getDistrict());
}

3.1.3、Use (必选)

3.1.3.1、use = JsonTypeInfo. Id.NAME

使用JsonTypeInfo的值作为识别码的值,如果有多个子类的情况,必须有 @JsonSubTypes,否则无法判断是哪个子类

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})


序列化Man :【 {"@type":"man","district":"北京","manField":"男人"} 】
子类转父类 ======================
反序列化man -> Human :【 {"@type":"man","district":"北京","manField":"男人"} 】
human.getDistrict()  :【 北京 】
  • **use = JsonTypeInfo.Id.NAME, 如果不写property ,则识别码 的名字默认为 @type **

  • 根据Json数据,识别之后,其值为@JsonSubTypes.Type(value = Human.Man.class, name = "man")中子类Man的name值

1、配置 property = “type”

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME
			  property = "type"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})

序列化Man :【 {"type":"man","district":"北京","manField":"男人"} 】 
子类转父类 ======================
反序列化man -> Human :【 {"type":"man","district":"北京","manField":"男人"} 】
human.getDistrict()  :【 北京 】 
  • 这个时候默认"@type就变成了type,如果换成其他的use,则默认值失效,变成这里的property 的值

3.1.3.2、use = JsonTypeInfo. Id.CLASS、property = “type”

**用类的全路劲名称来作为识别码的值,和@JsonSubType没关系,写上了也不起作用,可以自动判断有哪些子类 **

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
        	  property = "type"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})

序列化Man :【 {"type":"com.healerjean.proj.a_test.json.jackson.d02_JsonType.Human$Man","district":"北京","manField":"男人"} 】 
反序列化man -> Human :【 {"type":"com.healerjean.proj.a_test.json.jackson.d02_JsonType.Human$Man","district":"北京","manField":"男人"} 】 
human.getDistrict()  :【 北京 】 
  • **use = JsonTypeInfo.Id.CLASS, 如果不写property ,则识别码 的名字默认为 @class **

  • 根据Json数据,识别之后,值为,这个子类所在类路径,用.连接

1、这个时候发现和@JsonSubTypes中的值似乎没有什么关系,我果断去掉@JsonSubTypes属性
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)



序列化Man :【 {"@class` ":"com.healerjean.proj.a_test.json.jackson.d02_JsonType.Human$Man","district":"北京","manField":"男人"} 】 
反序列化man -> Human :【 {"@class` ":"com.healerjean.proj.a_test.json.jackson.d02_JsonType.Human$Man","district":"北京","manField":"男人"} 】 
human.getDistrict()  :【 北京 】 

3.1.3.3、use = JsonTypeInfo. Id.MINIMAL_CLASS

**表示具有最小路径的Java类名称用作识别码的值,和@JsonSubType没关系,写上了也不起作用,可以自动判断有哪些子类 **

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)


序列化Man :【 {"@c":".Human$Man","district":"北京","manField":"男人"} 】
子类转父类 ====================== 
反序列化man -> Human :【 {"@c":".Human$Man","district":"北京","manField":"男人"} 】human.getDistrict()  :【 北京 】

  • **use = JsonTypeInfo.Id.CLASS, 如果不写property ,则识别码 的名字默认为 @c **

3.1.4、include (可选)

默认是 JsonTypeInfo.As.PROPERTY,上面所有的都是以它进行测试的

3.1.4.1、include = JsonTypeInfo.As.WRAPPER_OBJECT

作为一个包装的对象

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.WRAPPER_OBJECT,
        property = "type"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})


序列化Man :【 {"man":{"district":"北京","manField":"男人"}} 】
子类转父类 ====================== 
反序列化man -> Human :【 {"man":{"district":"北京","manField":"男人"}} 】
human.getDistrict()  :【 北京 】 

3.1.4.2、include = JsonTypeInfo.As.WRAPPER_ARRAY

作为一个包装的数组看下面,这个数组可是不太规则哦,就是说里面放的不一定的同类型的),和有无property无关,看下面的测试打印结果就知道

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.WRAPPER_ARRAY,
        property = "type"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})


序列化Man :【 ["man",{"district":"北京","manField":"男人"}] 】
子类转父类 ====================== 
反序列化man -> Human :【 ["man",{"district":"北京","manField":"男人"}] 】
human.getDistrict()  :【 北京 】

3.1.4.3、include = JsonTypeInfo.As.EXTERNAL_PROPERTY

作为一个额外的属性,跟POJO同级,只能用于属性,如何作用于类则跟JsonTypeInfo.As.PROPERTY是相同效果

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
        property = "type"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})


序列化Man :【 {"type":"man","district":"北京","manField":"男人"} 】
类转父类 ======================
反序列化man -> Human :【 {"type":"man","district":"北京","manField":"男人"} 】
human.getDistrict()  :【 北京 】

3.1.4.4、include = JsonTypeInfo.As.EXISTING_PROPERTY

反序列化 :

  • 这个属性可是骚的厉害了,它要求property识别码的名称必须在被该属性表示的类中也存在一份,否则反序列化就会报错。
  • 反序列化的时候这个识别码名字属性被认为是识别码,如果这个识别码的值,不在@JsonSubTypes 的name中就会报错
  • 再者,反序列化之后这个属性值就不存在了,字段值也不存在了,除非再给对象重新赋值,这个值必须和我们真正意义上的反序列化的子类的JsonSubTypes 中 name一样,不可以任意挑选,否则会报错

序列化 :

  • 跟它没半毛钱关系,和整个注解也没关系

序列化的时候,跟它没半毛钱关系,但是不会像上面的那样

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "type"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})
public class Human {

    private String district;

    private String type;

    @Data
    public static class Man extends Human {
        private String manField;
    }

    @Data
    public static class Woman extends Human {
        private String womanField;
    }

}




序列化Man :【 {"district":"北京","type":"man","manField":"男人"} 】
子类转父类 ======================
 反序列化man -> Human :【 {"district":"北京","type":null,"manField":"男人"} 】
 human.getDistrict()  :【 北京 】
 

2.1.5、property (可选)

设置识别码是名称,根据use的属性,一般情况下有默认值

除了JsonTypeInfo.As.EXISTING_PROPERTY 有些特殊之外,JsonTypeInfo.As.EXTERNAL_PROPERTY、JsonTypeInfo.As.PROPERTY 注解的类中有了相同的属性(剩下的不讲解,因为其他的和property 没半毛钱关系),则会出现两次 以JsonTypeInfo.As.PROPERTY 举例

注意点

  • 在visible 为默认值 false的情况下反序列化, 虽然会出现两次,但是在位置上有严格要求,我测试过好多次,发现这个标识码肯定是靠前的,而且不能删除,删除就错了(当然啊,删除了,怎么判断你是哪个子类)

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
        property = "type"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})
public class Human {

    private String district;

    private String type;

    @Data
    public static class Man extends Human {
        private String manField;
    }

    @Data
    public static class Woman extends Human {
        private String womanField;
    }

}



   @Test
    public void testOne() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Man man = new Man();
        man.setManField("男人");
        man.setDistrict("北京");
        man.setType("lalala");

        String manJson = mapper.writeValueAsString(man);
        log.info("序列化Man :【 {} 】", manJson);
        Human human = mapper.readValue(manJson, Human.class);
        log.info("子类转父类 ======================");
        String humanJson = mapper.writeValueAsString(human);
        log.info("反序列化man -> Human :【 {} 】", humanJson);
        log.info("human.getDistrict()  :【 {} 】",  human.getDistrict());
        log.info("human.getType()  :【 {} 】",  human.getType());
    }



序列化Man :【 {"type":"man","district":"北京","type":"lalala","manField":"男人"} 
子类转父类 ======================
反序列化man -> Human :【 {"type":"man","district":"北京","type":"lalala","manField":"男人"} 】 
human.getDistrict()  :【 北京 】
 human.getType()  :【 lalala 】 
  

2.1.6、visible(可选)

visible(可选):定义识别码在反序列化(反序列为对象内部是否保留给字段)时是否保留(不管false或true都不影响序列化)。默认是false,表示Jackson可以将识别码识别为类型后就删除。

 @Test
    public void testOne() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Man man = new Man();
        man.setManField("男人");
        man.setDistrict("北京");
        man.setType("1");

        String manJson = mapper.writeValueAsString(man);
        log.info("序列化Man :【 {} 】", manJson);
        //只剩下一个type,必须放在第一个,这样才能识别子类
        manJson = "{\"type\":\"man\",\"district\":\"北京\",\"manField\":\"男人\"} ";
        Human human = mapper.readValue(manJson, Human.class);
        log.info("子类转父类 ======================");
        String humanJson = mapper.writeValueAsString(human);
        log.info("反序列化man -> Human :【 {} 】", humanJson);
        log.info("human.getDistrict()  :【 {} 】",  human.getDistrict());
        log.info("human.getType()  :【 {} 】",  human.getType());
    }
    
    
    
  • 一般情况下 visible = false

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type",
        visible = false
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = Human.Man.class, name = "man"),
        @JsonSubTypes.Type(value = Human.Woman.class, name = "woman"),
})


序列化Man :【 {"type":"man","district":"北京","type":"1","manField":"男人"} 】
子类转父类 ====================== 
反序列化man -> Human :【 {"type":"man","district":"北京","type":null,"manField":"男人"} 】
human.getDistrict()  :【 北京 】
human.getType()  :【 null 】




  • visible = true

反序列化会把它当做一个属性值处理(保留给字段了),也就是说反序列化后还是可见的,如果在这个上面我测试的Json中再加一个type,那么这个这个识别码照样能打印,但是会失效

序列化Man :【 {"type":"man","district":"北京","type":"1","manField":"男人"} 】
子类转父类 ======================
反序列化man -> Human :【 {"type":"man","district":"北京","type":"man","manField":"男人"} 】
 human.getDistrict()  :【 北京 】
human.getType()  :【 man 】



如果在这个上面我测试的Json中再加一个type,那么这个这个识别码照样能打印,但是会失效
manJson = "{\"type\":\"man\",\"district\":\"北京\",\"type\":\"1\",\"manField\":\"男人\"} ";

打印结果
反序列化man -> Human :【 {"type":"man","district":"北京","type":"1","manField":"男人"} 】
human.getType()  :【 1 】

感兴趣的,欢迎添加博主微信

哈,博主很乐意和各路好友交流,如果满意,请打赏博主任意金额,感兴趣的在微信转账的时候,备注您的微信或者其他联系方式。添加博主微信哦。

请下方留言吧。可与博主自由讨论哦

微信 微信公众号 支付宝
微信 微信公众号 支付宝
posted @ 2019-10-30 20:35  HealerJean  阅读(2509)  评论(0编辑  收藏  举报