Jackson使用@JsonTypeInfo反序列化多态类型(根据标识解析为子类对象)

问题场景

jackson可以将多态类型JSON序列化. 但在反序列化时会因为找不到具体的类而失败.

举例:创建4个POJO类

@Data
public class AbstractTarget {
}

@Data
@EqualsAndHashCode(callSuper = true)
class HiveTarget extends AbstractTarget {
    private String schema;
    private String table;
    private String column;
}

@Data
@EqualsAndHashCode(callSuper = true)
class HBaseTarget extends AbstractTarget{
    private String namespace;
    private String table;
    private String columnFamily;
    private String column;
}

@Data
class Statistics {
    private List<AbstractTarget> targets;
}

测试方法

@Test
public void testDeserialize() throws JsonProcessingException {
    Statistics statistics = new Statistics();
    List<AbstractTarget> targets = new ArrayList<>();
    statistics.setTargets(targets);

    HiveTarget hiveTarget = new HiveTarget();
    hiveTarget.setSchema("s1");
    hiveTarget.setTable("t1");
    hiveTarget.setColumn("c1");
    targets.add(hiveTarget);

    HBaseTarget hBaseTarget= new HBaseTarget();
    hBaseTarget.setNamespace("ns2");
    hBaseTarget.setTable("t2");
    hBaseTarget.setColumnFamily("cf2");
    hBaseTarget.setColumn("c2");
    targets.add(hBaseTarget);

    // 序列化
    String statisticsStr = mapper.writeValueAsString(statistics);
    System.out.println(statisticsStr);
    
    // 反序列化
    Statistics parsedStatistics = mapper.readValue(statisticsStr, Statistics.class);
    System.out.println(parsedStatistics);
}

结果

{"targets":[{"schema":"s1","table":"t1","column":"c1"},{"namespace":"ns2","table":"t2","columnFamily":"cf2","column":"c2"}]}


com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `fresh.json.AbstractTarget` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (String)"{"targets":[{"schema":"s1","table":"t1","column":"c1"},{"namespace":"ns2","table":"t2","columnFamily":"cf2","column":"c2"}]}"; line: 1, column: 13] (through reference chain: fresh.json.Statistics["targets"]->java.util.ArrayList[0])
    ...

因此若要正确的反序列化,需要指定具体子类的标识。

方式一.使用类名作为标识

如下:使用类名作为标识符,并将标识符作为属性序列化,属性名称指定为"@class"。

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
        include = JsonTypeInfo.As.PROPERTY, 	
        property = "@class"
)
//等于@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS), 另外两个为类名作为标识的默认值
public class AbstractTarget {
}

序列化结果中会包含”@class“属性,反序列化时就会根据”@class“找到具体的类。

{"targets":[{"@class":"fresh.json.HiveTarget","schema":"s1","table":"t1","column":"c1"},{"@class":"fresh.json.HBaseTarget","namespace":"ns2","table":"t2","columnFamily":"cf2","column":"c2"}]}

实测发现当直接使用List序列化时(targets)会丢失"@class"属性,嵌套的列表和单独对象都没有这个问题。

...
System.out.println("serializing nested array-------");
System.out.println(mapper.writeValueAsString(statistics));
System.out.println("serializing object-------------");
System.out.println(mapper.writeValueAsString(hiveTarget));
System.out.println("serializing array--------------");
System.out.println(mapper.writeValueAsString(targets));

结果

serializing nested array-------
{"targets":[{"@class":"fresh.json.HiveTarget","type":null,"schema":"s1","table":"t1","column":"c1"},{"@class":"fresh.json.HBaseTarget","type":null,"namespace":"ns2","table":"t2","columnFamily":"cf2","column":"c2"}]}
serializing object-------------
{"@class":"fresh.json.HiveTarget","type":null,"schema":"s1","table":"t1","column":"c1"}
serializing array--------------
[{"type":null,"schema":"s1","table":"t1","column":"c1"},{"type":null,"namespace":"ns2","table":"t2","columnFamily":"cf2","column":"c2"}]

方式二.使用属性值作为标识

使用类名作为标识符的好处是配置方便,但是会在序列化中暴露类名,并且如果使用其他方式构造json串时可能需要手动设置”类名属性“。

另一种方式是使用属性值做为标识,配置较为繁琐,适合类中已经存在标识属性的情况。

如下:AbstractTarget存在type属性,并且在两个子类中设置了固定且不同的值,使用@JsonTypeInfo指定type属性作为”类标识“,同时需要使用@JsonSubTypes指定 具体类 和 type属性值 的关系。

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "type"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = HiveTarget.class, name = HiveTarget.TYPE),
        @JsonSubTypes.Type(value = HBaseTarget.class, name = HBaseTarget.TYPE)
})
public class AbstractTarget {
    private String type;
}
@Data
@EqualsAndHashCode(callSuper = true)
class HiveTarget extends AbstractTarget {
    private String schema;
    private String table;
    private String column;
    
    static final String TYPE = "hive";
    public HiveTarget(){
        setType(TYPE);
    }
}
@Data
@EqualsAndHashCode(callSuper = true)
class HBaseTarget extends AbstractTarget{
    private String namespace;
    private String table;
    private String columnFamily;
    private String column;

    static final String TYPE = "hbase";
    public HBaseTarget(){
        setType(TYPE);
    }
}

序列化结果就和普通的序列化一致,不会包含额外属性。

{"targets":[{"type":"hive","schema":"s1","table":"t1","column":"c1"},{"type":"hbase","namespace":"ns2","table":"t2","columnFamily":"cf2","column":"c2"}]}

Maven依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.2</version>
</dependency>

参考地址

Jackson里使用@JsonTypeInfo注解处理多态类型的序列化和反序列化

Jackson使用@JsonTypeInfo反序列化多态类型(根据标识解析为子类对象)

上一篇:直播应用web通信


下一篇:PHP练习