Jackson父子类多态处理(注解实现)
- 方案二的实现 1.1 @JsonTypeInfo注解 1.2 @JsonSubTypes注解 1.3 @JsonTypeName注解
- 案例
-
智能版:扩展@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(可选):制定识别码的属性名称:
此属性只有当:
- 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。
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);