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"}]}
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.2</version> </dependency>
参考地址
Jackson里使用@JsonTypeInfo注解处理多态类型的序列化和反序列化