首页 > 技术文章 > jackson解决多态发序列化问题

noah-sheng 2020-07-05 23:19 原文

在项目中经常会有多个子类继承一个抽象类或者是实现一个接口,当我们需要对接收到的消息进行反序列化时,就会出现问题,代码如下:

@Getter
@Setter
public abstract class MessageContent {

    private String contentType;

    /**
     * 消息内容(contentText)编码方式。
     * 默认为utf8字符编码,可选base64编码。
     */
    private String contentEncoding;

}

它有多个子类,分别为:

@Getter
@Setter
public class MessageContentBack extends MessageContent {

    /**
     * contentType为"text/plain" 内容为字符串,其余的类型 内容为json对象。
     * 如果编码式为base64,则内容为base64编码之后的字符
     */
    private String contentText;
}

以及:

@Getter
@Setter
public class ContentTextSuggestionChipList extends MessageContent {

    private Suggestion contentText;
}

异常:

Can not construct instance of XXXX, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information

下面我们来讲讲解决多态中反序列化问题的解决---jackson。
首先,先来看看几个重要注解以及它们的参数:

@JsonTypeInfo

use:定义使用哪一种类型识别码,它有下面几个可选值:
    1、JsonTypeInfo.Id.CLASS:使用完全限定类名做识别
    2、JsonTypeInfo.Id.MINIMAL_CLASS:若基类和子类在同一包类,使用类名(忽略包名)作为识别码
    3、JsonTypeInfo.Id.NAME:一个合乎逻辑的指定名称
    4、JsonTypeInfo.Id.CUSTOM:自定义识别码
    5、JsonTypeInfo.Id.NONE:不使用识别码
include(可选):指定识别码是如何被包含进去的,它有下面几个可选值:
    1、JsonTypeInfo.As.PROPERTY:作为数据的兄弟属性
    2、JsonTypeInfo.As.EXISTING_PROPERTY:作为POJO中已经存在的属性
    3、JsonTypeInfo.As.EXTERNAL_PROPERTY:作为扩展属性
    4、JsonTypeInfo.As.WRAPPER_OBJECT:作为一个包装的对象
    5、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):propery中的属性是否反序列化到POJO中
属性定义了类型标识符的值是否会通过JSON流成为反序列化器的一部分,默认为fale,也就是说,jackson会从JSON内容中处理和删除类型标识符再传递给JsonDeserializer

@JsonSubtypes

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

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include =  JsonTypeInfo.As.EXISTING_PROPERTY ,property = "contentType", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = MessageContentBack.class, name = "text/plain"),
        @JsonSubTypes.Type(value = MessageContentFile.class, name = "application/vnd.gsma.rcs-ft-http"),
        @JsonSubTypes.Type(value = MessageContentReceiveResponse.class, name = "application/vnd.gsma.botsuggestion.response.v1.0+json"),
        @JsonSubTypes.Type(value = MessageContentSharedData.class, name = "application/vnd.gsma.botsharedclientdata.v1.0+json"),
})

特别说明:

刚开始在写代码时,由于不熟悉jackson,include属性选择的是JsonTypeInfo.As.PROPERTY,发现在对它的子类进行序列化的时候,property属性中的内容(这里是“contentType”)作为兄弟属性被序列化了一次,即序列化后的结果中出现两个相同的属性“contentType”。后来再查阅资料发现JsonTypeInfo.As.PROPERTY的意思是property中的属性作为数据的兄弟属性会被序列化一次,而JsonTypeInfo.As.EXITING_PROPERTY则是作为POJO中已经存在的属性被包含到序列化的结果中。

 

推荐阅读