多态序列化问题

172 阅读2分钟

记录在项目中遇到的一个多态序列化的问题

场景:

需要创建多个事件(click、MouseIn...),事件可能有多种不同的响应类型(弹窗、页面跳转)。

实体如下:

/**
 * 事件对象
 */
@Data
public class Event {
    /**
     * 事件编码
     */
    private String code;
    /**
     * 事件类型
     */
    private String eventType;
    /**
     * 事件响应
     */
    private EventResponse response;
​
}
​
/**
 * 响应对象的
 */
@Data
public class EventResponse {
  
}
​
​
/**
 * 页面跳转响应对象
 */
@Data
public class PageTurns extends EventResponse{
    /**
     * 响应类型为page
     */
    private final String responseType = "page";
    /**
     * 跳转页面的url
     */
    private String url;
}
​
/**
 * 弹窗响应对象
 */
@Data
public class PopupContent extends EventResponse{
​
    /**
     * 响应类型为popup
     */
    private final String responseType = "popup";
    /**
     * 弹窗内容
     */
    private String content;
}
​

接收的VO是Event,可以看到当响应类型不同时接收的参数不同,创建的对象类型也不同。

也就是希望能在序列化参数时自动帮我们序列化为子类对象,这也就是我提到多态序列化问题。

解决

@JsonTypeInfo注解作用于类/接口可以开启多态类型处理

该注解有如下几个属性

  1. use:定义使用哪一种类型识别码

    image-20230713232346139

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

    image-20230713232513064

  3. property(可选):识别码的名称,根据 use的属性值不同,默认值不同

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

@JsonSubTypes 配置可以匹配的子类型

我们这里选择的

  1. use: JsonTypeInfo.Id.NAME 按照JsonTypeInfo的值作为匹配码来匹配
  2. include:JsonTypeInfo.As.EXTERNAL_PROPERTY 匹配码是一个与类属性同级的额外属性
  3. property:responseType
  4. visible :true
/**
 * 事件对象
 */
@Data
public class Event {
    /**
     * 事件编码
     */
    private String code;
    /**
     * 事件类型
     */
    private String eventType;
​
    private String responseType;
    /**
     * 事件响应
     */
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, //按照JsonTypeInfo的值作为匹配码
            include = JsonTypeInfo.As.EXTERNAL_PROPERTY, //与类属性同级的额外属性
            property = "responseType") //额外属性的属性名为 responseType
    @JsonSubTypes({
            @JsonSubTypes.Type(value = PageTurns.class, name = "page"),
            @JsonSubTypes.Type(value = PopupContent.class, name = "popup"),
    })
    private EventResponse response;
​
}
​
/**
 * 响应对象
 */
@Data
public class EventResponse {
​
}
​
/**
 * 页面跳转响应对象
 */
@Data
public class PageTurns extends EventResponse{
​
    private final String responseType = "page" ;
    /**
     * 跳转页面的url
     */
    private String url;
}
/**
 * 弹窗响应对象
 */
@Data
public class PopupContent extends EventResponse{
​
    private final String responseType = "popup" ;
    /**
     * 弹窗内容
     */
    private String content;
}

测试

 // 通过controller接收 Event
@PostMapping("/multilstate/serialize")
public Result<Event> createEvent(@RequestBody Event event){
  if (event.getResponse() instanceof PopupContent){
    System.out.println("创建了一个响应类型为 PopupContent 的事件 event: "+event);
  }else if ( event.getResponse() instanceof PageTurns){
    System.out.println("创建了一个响应类型为 PageTurns 的事件 event: "+event);
  }
  return Result.success(event);
}
​
// 请求参数1
{
    "code":"001",
    "eventType":"click",
    "responseType":"page",
    "response":{
        "url":"https://www.baidu.com"
    }
}
/*
控制台输出
创建了一个响应类型为 PageTurns 的事件 event: Event(code=001, eventType=click, responseType=page, response=PageTurns(responseType=page, url=https://www.baidu.com))
*/

参考

Jackson多态处理