@JsonTypeInfo
作用于类/接口,被用来开启多态类型处理,对基类/接口和子类/实现类都有效。
注解属性:
use:定义使用哪一种类型识别码,它有下面几个可选值:
JsonTypeInfo.Id.CLASS
:使用完全限定类名做识别JsonTypeInfo.Id.MINIMAL_CLASS
:若基类和子类在同一包类,使用类名(忽略包名)作为识别码JsonTypeInfo.Id.NAME
:一个合乎逻辑的指定名称JsonTypeInfo.Id.CUSTOM
:自定义识别码,由@JsonTypeIdResolver
对应,稍后解释JsonTypeInfo.Id.NONE
:不使用识别码include(可选):指定识别码是如何被包含进去的,它有下面几个可选值:
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
时才有效@JsonSubTypes
作用于类/接口,用来列出给定类的子类,只有当子类类型无法被检测到时才会使用它,一般是配合@JsonTypeInfo
在基类上使用。
@JsonSubTypes
的值是一个@JsonSubTypes.Type[]
数组,里面枚举了多态类型(value
对应子类)和类型的标识符值(name
对应@JsonTypeInfo
中的property标识名称的值,此为可选值,若不制定需由@JsonTypeName
在子类上制定)
Qring需要创建一个优惠券模板,在创建的时候希望能够只通过调用一个接口方法即可,不希望记住太多的方法接口,在创建的时候会传入不同的模板信息。
根据面向对象思想我们抽象出了一个优惠券信息父类,在它的下面有不同的子类,比如折扣券、礼品券,我们定了两个接口,提供给Qring创建券模板以及获取券模板信息。
在创建券模板时,我们只有一个创建券模板的接口,而券模板里面只放着优惠券信息的父类,如果不进行标识,Jackson是无法知道我们究竟需要创建的是哪一种券的券模板,因此我们需要使用注解@JsonTypeInfo
为优惠券信息父类打上标识告诉Jackson在反序列化时究竟要怎么做,同时使用@JsonSubTypes
说明在优惠券信息父类下有哪些子类。
在获取券模板信息时,我们需要配合@TableName(autoResultMap = true)
以及@TableField(typeHandler = JacksonTypeHandler.class)
让mybatis-plus知道如何进行反序列化。
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = GiftCoupon.class, name = "GiftCoupon"),
@JsonSubTypes.Type(value = DiscountCoupon.class, name = "DiscountCoupon"),
})
public abstract class Coupon {
}
@Data
public class DiscountCoupon extends Coupon {
private String type;
private Double discount;
}
@Data
public class GiftCoupon extends Coupon {
private String type;
private String giftName;
private Integer value;
}
@Data
@TableName(value = "coupon_template", autoResultMap = true)
public class CouponTemplate {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField(value = "coupon", typeHandler = JacksonTypeHandler.class)
private Coupon coupon;
}
@GetMapping("/getCouponTemplate")
ResultData<CouponTemplate> getCouponTemplate(@RequestParam Integer id) {
return ResultData.success(couponTemplateMapper.selectById(id));
}
@PostMapping("/createCouponTemplate")
ResultData<Integer> getCouponTemplate(@RequestBody CouponTemplate couponTemplate) {
return ResultData.success(couponTemplateMapper.insert(couponTemplate));
}
如果@JsonTypeInfo的include使用的不是JsonTypeInfo.As.EXISTING_PROPERTY,那么在反序列时返回的数据会出现问题,标记字段会出现两次,如果设置visible = false,那么其中一个会为null,如果从数据库查询直接返回给前端可能会出现问题。
{
"status": 100,
"message": "操作成功",
"data": {
"id": 5,
"coupon": {
"type": "DiscountCoupon",
"type": "DiscountCoupon",
"discount": 0.95
}
},
"timestamp": 1657607815210
}
跟踪ObjectMapper源码时发现,如果是JsonTypeInfo.As.EXISTING_PROPERTY,那么在JsonGenerator.writeTypePrefix()的swtich时走的是PAYLOAD_PROPERTY分支,不进行任何处理,而其他类型会进行相应的处理,如JsonTypeInfo.As.EXTERNAL_PROPERTY会将type
标记字段进行写入,之后再将bean的属性写入,因此出现了两个相同的字段。具体过程可跟踪ObjectMapper源码进行深入了解。
public WritableTypeId writeTypePrefix(WritableTypeId typeIdDef) throws IOException {
Object id = typeIdDef.id;
JsonToken valueShape = typeIdDef.valueShape;
if (this.canWriteTypeId()) {
typeIdDef.wrapperWritten = false;
this.writeTypeId(id);
} else {
String idStr = id instanceof String ? (String)id : String.valueOf(id);
typeIdDef.wrapperWritten = true;
Inclusion incl = typeIdDef.include;
if (valueShape != JsonToken.START_OBJECT && incl.requiresObjectContext()) {
typeIdDef.include = incl = Inclusion.WRAPPER_ARRAY;
}
switch(incl) {
case PARENT_PROPERTY:
case PAYLOAD_PROPERTY:
break;
case METADATA_PROPERTY:
this.writeStartObject(typeIdDef.forValue);
this.writeStringField(typeIdDef.asProperty, idStr);
return typeIdDef;
case WRAPPER_OBJECT:
this.writeStartObject();
this.writeFieldName(idStr);
break;
case WRAPPER_ARRAY:
default:
this.writeStartArray();
this.writeString(idStr);
}
}
if (valueShape == JsonToken.START_OBJECT) {
this.writeStartObject(typeIdDef.forValue);
} else if (valueShape == JsonToken.START_ARRAY) {
this.writeStartArray();
}
return typeIdDef;
}