java info类型_Java Jackson @JsonTypeInfo 多态类型处理

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

@JsonTypeInfo

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

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include = JsonTypeInfo.As.PROPERTY,property = "name")

这个注解有一些属性:

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中已经存在的属性

JsonTypeInfo.As.EXTERNAL_PROPERTY:作为扩展属性

JsonTypeInfo.As.WRAPPER_OBJECT:作为一个包装的对象

JsonTypeInfo.As.WRAPPER_ARRAY:作为一个包装的数组

property(可选):制定识别码的属性名称

此属性只有当:

use为JsonTypeInfo.Id.CLASS(若不指定property则默认为@class)、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property则默认为@c)、JsonTypeInfo.Id.NAME(若不指定property默认为@type),

include为JsonTypeInfo.As.PROPERTY、JsonTypeInfo.As.EXISTING_PROPERTY、JsonTypeInfo.As.EXTERNAL_PROPERTY时才有效

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

visible(可选,默认为false):是否可见

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

@JsonSubTypes

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

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include = JsonTypeInfo.As.PROPERTY,property = "typeName")

@JsonSubTypes({@JsonSubTypes.Type(value=Sub1.class,name = "sub1"),@JsonSubTypes.Type(value=Sub2.class,name = "sub2")})

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

@JsonTypeName

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

比如:

@JsonTypeName(value = "sub1")

示例

基类:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,property = "type")

@JsonSubTypes({@JsonSubTypes.Type(value= Test.RoleUser.class,name = "role"),@JsonSubTypes.Type(value= Test.TokenUser.class,name = "token")})

public abstract class AbstractBaseEntity {

private String userName;

private String password;

public String getUserName() {

return userName;

}

public AbstractBaseEntity setUserName(String userName) {

this.userName = userName;

return this;

}

public String getPassword() {

return password;

}

public AbstractBaseEntity setPassword(String password) {

this.password = password;

return this;

}

@Override

public String toString() {

final StringBuffer sb = new StringBuffer("AbstractBaseEntity{");

sb.append("userName='").append(userName).append('\'');

sb.append(", password='").append(password).append('\'');

sb.append('}');

return sb.toString();

}

}

测试类

import com.fasterxml.jackson.databind.ObjectMapper;

/**

* @author micocube

* projectName: utils4j

* packageName: jackson

* email: ldscube@gmail.com

* createTime: 2019-07-05 11:23

* version: 0.1

* description:

*/

public class Test {

# 子类

public static class TokenUser extends AbstractBaseEntity {

private String token;

public String getToken() {

return token;

}

public TokenUser setToken(String token) {

this.token = token;

return this;

}

@Override

public String toString() {

final StringBuffer sb = new StringBuffer("TokenUser{");

sb.append("token='").append(token).append('\'');

sb.append('}');

sb.append(super.toString());

return sb.toString();

}

}

# 子类

public static class RoleUser extends AbstractBaseEntity {

private String roleName;

public String getRoleName() {

return roleName;

}

public RoleUser setRoleName(String roleName) {

this.roleName = roleName;

return this;

}

@Override

public String toString() {

final StringBuffer sb = new StringBuffer("RoleUser{");

sb.append("roleName='").append(roleName).append('\'');

sb.append('}');

sb.append(super.toString());

return sb.toString();

}

}

public static void main(String[] args) throws Exception{

ObjectMapper objectMapper = new ObjectMapper();

RoleUser roleUser = new RoleUser();

roleUser.setRoleName("role");

roleUser.setPassword("rolePwd");

roleUser.setUserName("roleUserName");

TokenUser tokenUser = new TokenUser();

tokenUser.setToken("token");

tokenUser.setPassword("tokenPassword");

tokenUser.setUserName("tokenUserName");

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);

}

}

程序输出

{"type":"jackson.Test$RoleUser","userName":"roleUserName","password":"rolePwd","roleName":"role"}

{"type":"jackson.Test$TokenUser","userName":"tokenUserName","password":"tokenPassword","token":"token"}

RoleUser{roleName='role'}AbstractBaseEntity{userName='roleUserName', password='rolePwd'}

TokenUser{token='token'}AbstractBaseEntity{userName='tokenUserName', password='tokenPassword'}

因为基类中use = JsonTypeInfo.Id.CLASS,property = "type",序列化时(输出的第一行和第二行type值为class限定名),若改为use = JsonTypeInfo.Id.NAME,property = "type",那么输出如下,use是Name,取值为JsonSubTypes的name,属性名为type

{"type":"role","userName":"roleUserName","password":"rolePwd","roleName":"role"}

{"type":"token","userName":"tokenUserName","password":"tokenPassword","token":"token"}

RoleUser{roleName='role'}AbstractBaseEntity{userName='roleUserName', password='rolePwd'}

TokenUser{token='token'}AbstractBaseEntity{userName='tokenUserName', password='tokenPassword'}

参考资料

十分钟学习Jackson多态处理

上案例:

public abstract class Animal {

private String name;

//忽略getter和setter

}

public class Elephant extends Animal{

}

public class Monkey extends Animal{

}

Elephant elephant = new Elephant();

elephant.setName("大象精");

Monkey monkey = new Monkey();

monkey.setName("孙悟空");

ObjectMapper objectMapper = new ObjectMapper();

System.out.println(objectMapper.writeValueAsString(elephant));

System.out.println(objectMapper.writeValueAsString(monkey));

输出:

{"name":"大象精"}

{"name":"孙悟空"}

嗯~ o( ̄▽ ̄)o,是可以序列化,但好像没有办法识别他们的类型啊。

@JsonTypeInfo注解开启多态处理

修改下代码:

@JsonTypeInfo(

use=Id.CLASS

)

public abstract class Animal {

private String name;

//忽略getter和setter

}

重新执行,输出:

{"@class":"com.tinylynn.springboot.json.type.Elephant","name":"大象精"}

{"@class":"com.tinylynn.springboot.json.type.Monkey","name":"孙悟空"}

看到不一样的东西了,Jackson帮我们添加了一个@class节点,值是完全限定类名

@JsonTypeInfo注解的说明

它有一些属性:

use(必选):定义使用哪一种类型识别码,可选值有多种:

JsonTypeInfo.Id.NONE:不使用识别码

{"name":"大象精"}

{"name":"孙悟空"}

JsonTypeInfo.Id.CLASS:使用完全限定类名做识别,识别码名称@class

{"@class":"com.tinylynn.springboot.json.type.Elephant","name":"大象精"}

{"@class":"com.tinylynn.springboot.json.type.Monkey","name":"孙悟空"}

JsonTypeInfo.Id.MINIMAL_CLASS:表示具有最小路径的Java类名称用作识别,识别码名称@c(慎用)

{"@c":".Elephant","name":"大象精"}

{"@c":".Monkey","name":"孙悟空"}

JsonTypeInfo.Id.NAME:使用类型的名称做识别,识别码名称@type

{"@type":"Elephant","name":"大象精"}

{"@type":"Monkey","name":"孙悟空"}

JsonTypeInfo.Id.CUSTOM:自定义识别码,需结合property属性和@JsonTypeIdResolver注释,后面给出案例。

include(可选):设置识别码包含在哪里。

JsonTypeInfo.As.PROPERTY:作为POJO的属性出现(默认)

JsonTypeInfo.As.WRAPPER_OBJECT:作为一个包装的对象

@JsonTypeInfo(

use=Id.NAME,

include=As.WRAPPER_OBJECT

)

public abstract class Animal {

//...

}

输出:

{"Elephant":{"name":"大象精"}}

{"Monkey":{"name":"孙悟空"}}

JsonTypeInfo.As.WRAPPER_ARRAY:作为一个包装的数组

["Elephant",{"name":"大象精"}]

["Monkey",{"name":"孙悟空"}]

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

public class Zoo {

@JsonTypeInfo(use=Id.NAME,include=As.EXTERNAL_PROPERTY)

private Animal animal;

//动物园名称

private String name;

//忽略getter和setter

}

Zoo zoo = new Zoo();

zoo.setName("只有一只大象的动物园");

zoo.setAnimal(elephant);

System.out.println(objectMapper.writeValueAsString(zoo));

输出:

{"animal":{"name":"大象精"},"@type":"Elephant","name":"只有一只大象的动物园"}

JsonTypeInfo.As.EXISTING_PROPERTY:反序列化的时候,跟JsonTypeInfo.As.PROPERTY的处理相同;序列化,则Jackson不主动处理,由我们自行处理。

序列化:POJO -> JSON

反序列化:JSON -> POJO

上案例就清楚啦:

@JsonTypeInfo(

use=Id.NAME,

include=As.EXISTING_PROPERTY,

property="type" //设置识别码名称为type,跟字段type名称一样。

)

@JsonSubTypes({ //设置对应子类的识别码值

@Type(value = Monkey.class, name = "猴子") ,

@Type(value = Elephant.class, name = "大象")

})

public abstract class Animal {

private String type; //新增类型

private String name;

//忽略getter和setter

}

ObjectMapper objectMapper = new ObjectMapper();

Elephant elephant = new Elephant();

elephant.setName("孤单的大象");

String elephantJson = objectMapper.writeValueAsString(elephant); System.out.println(elephantJson);

Elephant anotherElephant = new Elephant();

anotherElephant.setName("另一头孤单的大象");

anotherElephant.setType("大象");

String anotherElephantJson = objectMapper.writeValueAsString(anotherElephant);

System.out.println(anotherElephantJson);

输出:

{"type":null,"name":"孤单的大象"}

{"type":"大象","name":"另一头孤单的大象"}

说明include=As.EXISTING_PROPERTY在序列化的时候Jackson不会处理识别码。

String deElephant = "{\"type\":\"大象\",\"name\":\"另一头孤单的大象\"}";

Animal elephant = objectMapper.readValue(deElephant, Animal.class);

System.out.println(elephant instanceof Elephant); //true

在反序列化时候,type的值被认为是识别码,如果type的值不是[大象,猴子]其中之一,则程序会抛出异常。

property(可选):设置识别码是名称,在include=JsonTypeInfo.As.PROPERTY或use=JsonTypeInfo.Id.CUSTOM生效。其他情况使用默认的识别码名称。

注意:include=JsonTypeInfo.As.PROPERTY和property同时存在有个问题,如果POJO具有相同名称的属性,会出现两个..

上案例:

@JsonTypeInfo(

use=Id.NAME,

include=As.PROPERTY,

property="type" //设置识别码名称为type,跟字段type名称一样。

)

@JsonSubTypes({ //设置对应子类的识别码值

@Type(value = Monkey.class, name = "猴子") ,

@Type(value = Elephant.class, name = "大象")

})

public abstract class Animal {

private String type;

private String name;

//忽略getter和setter

}

Elephant elephant = new Elephant();

elephant.setType("猴子");//故意设置

elephant.setName("我是什么动物");

ObjectMapper objectMapper = new ObjectMapper();

System.out.println(objectMapper.writeValueAsString(elephant));

输出:

{"type":"大象","type":"猴子","name":"我是什么动物"}

好神奇,两个type。其实好理解啦,include=As.PROPERTY告诉Jackson 识别码是作为POJO的属性出现,而同时你告诉Jackson识别码名称为type,Jackson才不管你POJO是不是已经有包含type属性,都给你输出。如果没有设置property属性,则使用默认的识别码名称,就是@type。

比这个更神奇的,上案例:

Elephant elephant = new Elephant();

elephant.setType("我是大象");

elephant.setName("安安");

Monkey monkey = new Monkey();

monkey.setType("我是猴子");

monkey.setName("宁宁");

List list = Lists.newArrayList(elephant, monkey);

ObjectMapper objectMapper = new ObjectMapper();

System.out.println(objectMapper.writeValueAsString(list));

输出:

[{"type":"我是大象","name":"安安"},{"type":"我是猴子","name":"宁宁"}]

没有识别码,被吃掉了!!!什么原因不知道,记住这个坑就好了!!

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

看不懂,上案例:

@JsonTypeInfo(

use = Id.NAME,

include = As.PROPERTY,

property = "type", //跟type属性同名,

visible = false

)

@JsonSubTypes({

@Type(value = Monkey.class, name = "猴子"),

@Type(value = Elephant.class, name = "大象")

})

public abstract class Animal {

private String type;

private String name;

//忽略getter和setter

}

Elephant elephant = new Elephant();

elephant.setName("安安");

ObjectMapper objectMapper = new ObjectMapper();

//序列化,注意这边是会出现两个type

String json = objectMapper.writeValueAsString(elephant);

System.out.println(json);

//反序列化,注意这边只有一个type,但它是作为识别码被Jackson识别的

String deJson = "{\"type\":\"大象\",\"name\":\"安安\"}";

Animal animal = objectMapper.readValue(deJson, Animal.class);

System.out.println(animal instanceof Elephant);

System.out.println(animal.getType());

输出:

{"type":"大象","type":null,"name":"安安"}

true //说明识别码是有效的

null //说明Jackson处理完识别码就删除了

将visible改为true再执行一遍:

{"type":"大象","type":null,"name":"安安"}

true

大象

总结下,visible=true和include=As.EXISTING_PROPERTY配合比较好。上面有提到@JsonSubTypes,那么这个注解做什么的呢。

@JsonSubTypes注解的说明

和@JsonTypeInfo一起使用的注解,声明可序列化多态类型的子类型,以及名称。只有和use = Id.NAME,才会使用@JsonSubTypes定义的名称。

使用@JsonTypeName注解

@JsonSubTypes作用在超类,有时候开发并没有办法可以直接修改,那么新增的子类要如何定义呢?

上案例:

@JsonTypeName("国宝熊猫")

public class Panda extends Animal{

}

Panda panda = new Panda();

panda.setName("贝贝");

ObjectMapper objectMapper = new ObjectMapper();

//序列化

String json = objectMapper.writeValueAsString(panda);

System.out.println(json);

输出:

{"type":"国宝熊猫","type":null,"name":"贝贝"}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值