本文介绍怎么在Jackson里实现多态类的序列化与反序列化。
目录
简介
在Java中,我们通常会定义基类(接口/抽象类),然后实际用到是其子类。这会在反序列化的时候带来问题,因为JSON本身是没有schema的,它在反序列化的时候根本不知道应该用哪个子类来对应。所以为了能够反序列化,通常要在JSON里加入一些额外的信息才能正确的反序列化。注意:这样的Java对象序列化成JSON后其它语言可能很难做对应的反序列化(到对象),因此更多的用于Java语言内部使用。请在设计对外的API接口时尽量不用这样的POJO!
测试POJO类
我们首先定义几个类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public abstract class Vehicle {
private String make;
private String model;
}
Vehicle是一个抽象的类,包括制造商(make)和车型(model)两个字段。
@Data
@NoArgsConstructor
public class Car extends Vehicle {
private int seatingCapacity;
private double topSpeed;
public Car(String make, String model, int seatingCapacity, double topSpeed) {
super(make, model);
this.seatingCapacity = seatingCapacity;
this.topSpeed = topSpeed;
}
}
Car是Vehicle的一个子类,它增加了座位数(seatingCapacity)和最高时速(topSpeed)两个字段。
@Data
@NoArgsConstructor
public final class Truck extends Vehicle {
private double payloadCapacity;
public Truck(String make, String model, double payloadCapacity) {
super(make, model);
this.payloadCapacity = payloadCapacity;
}
}
Truck是另一个子类,它增加了载重(payload capacity)这个字段。
@Data
@NoArgsConstructor
public class Trailer extends Car{
private String trailerType;
public Trailer(String trailerType, String make, String model,
int seatingCapacity, double topSpeed){
super(make, model, seatingCapacity, topSpeed);
this.trailerType = trailerType;
}
}
Trailer(房车)是Car的一个子类,它增加了一个拖车类型的字段。
子类的问题
我们首先定义一个Cars类,假设用它来存储多个Car。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Cars {
private List<Car> cars;
}
下面是测试代码:
public class TestCarList {
public static void main(String[] args) throws JsonProcessingException {
List<Car> carList = new ArrayList<>();
carList.add(new Car("BMW", "S500", 5, 250.0));
carList.add(new Car("BMW", "S600", 5, 300.0));
Cars cars = new Cars(carList);
ObjectMapper objectMapper = new ObjectMapper();
ObjectWriter writer = objectMapper.writer(new DefaultPrettyPrinter());
String json = writer.writeValueAsString(cars);
System.out.println(json);
Cars cars2 = objectMapper.readValue(json, Cars.class);
json = writer.writeValueAsString(cars2);
System.out.println(json);
}
}
上面的代码是没有任何问题的,因为Cars类定义里保存了List
public class TestCarList2 {
public static void main(String[] args) throws JsonProcessingException {
List<Car> carList = new ArrayList<>();
carList.add(new Car("BMW", "S500", 5, 250.0));
carList.add(new Car("BMW", "S600", 5, 300.0));
carList.add(new Trailer("a", "BMW", "S700", 8, 150.));
Cars cars = new Cars(carList);
ObjectMapper objectMapper = new ObjectMapper();
ObjectWriter writer = objectMapper.writer(new DefaultPrettyPrinter());
String json = writer.writeValueAsString(cars);
System.out.println(json);
Cars cars2 = objectMapper.readValue(json, Cars.class);
json = writer.writeValueAsString(cars2);
System.out.println(json);
}
}
上面的代码里,我们往carList里放了一个Trailer对象。把Cars变成JSON是没有问题的,其输出为:
{
"cars" : [ {
"make" : "BMW",
"model" : "S500",
"seatingCapacity" : 5,
"topSpeed" : 250.0
}, {
"make" : "BMW",
"model" : "S600",
"seatingCapacity" : 5,
"topSpeed" : 300.0
}, {
"make" : "BMW",
"model" : "S700",
"seatingCapacity" : 8,
"topSpeed" : 150.0,
"trailerType" : "a"
} ]
}
但是反序列化时会抛出异常:com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field “trailerType”。因为Jackson看到的是List
类似的,我们通常会定义抽象基类或者接口,如下:
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Fleet {
private List<Vehicle> vehicles;
}
那么也会出现错误,测试代码如下:
public class TestPolymorphicType {
public static void main(String[] args) throws JsonProcessingException {
List<Vehicle> vehicleList = new ArrayList<>();
vehicleList.add(new Car("BMW", "S500", 5, 250.0));
vehicleList.add(new Truck("Isuzu", "NQR", 7500.0));
Fleet fleet = new Fleet(vehicleList);
ObjectMapper objectMapper = new ObjectMapper();
ObjectWriter writer = objectMapper.writer(new DefaultPrettyPrinter());
String json = writer.writeValueAsString(fleet);
System.out.println(json);
Fleet fleet2 = objectMapper.readValue(json, Fleet.class);
json = writer.writeValueAsString(fleet2);
System.out.println(json);
}
}
异常为:InvalidDefinitionException: Cannot construct instance of com.github.fancyerii.learnjackson.a02.Vehicle
。很显然,它尝试构造Vehicle对象,但是Vehicle是个抽象类,不可能构造出来。
设置全局默认多态类型
为了解决这个问题,Jackson可以设置ObjectMapper的全局默认多态类型。具体来说通过如下函数设置:
ObjectMapper.activateDefaultTyping(PolymorphicTypeValidator ptv,
ObjectMapper.DefaultTyping applicability, JsonTypeInfo.As includeAs)
这个方法看起来有点复杂,我们通过一个具体的例子来看看它的用法:
public class TestDefaultTyping {
public static void main(String[] args) throws JsonProcessingException {
List<Vehicle> vehicleList = new ArrayList<>();
vehicleList.add(new Car("BMW", "S500", 5, 250.0));
vehicleList.add(new Truck("Isuzu", "NQR", 7500.0));
Truck.Fleet fleet = new Truck.Fleet(vehicleList);
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.github.fancyerii.learnjackson.a02.pojo")
.allowIfSubType("java.util.ArrayList")
.build();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
ObjectWriter writer = objectMapper.writer(new DefaultPrettyPrinter());
String json = writer.writeValueAsString(fleet);
System.out.println(json);
Truck.Fleet fleet2 = objectMapper.readValue(json, Truck.Fleet.class);
json = writer.writeValueAsString(fleet2);
System.out.println(json);
}
}
这个例子和前面的基本一样,注意的区别是调用了ObjectMapper.activateDefaultTyping方法。我们首先来看程序的输出:
{
"@class" : "com.github.fancyerii.learnjackson.a02.Fleet",
"vehicles" : [ "java.util.ArrayList", [ {
"@class" : "com.github.fancyerii.learnjackson.a02.pojo.Car",
"make" : "BMW",
"model" : "S500",
"seatingCapacity" : 5,
"topSpeed" : 250.0
}, {
"@class" : "com.github.fancyerii.learnjackson.a02.pojo.Truck",
"make" : "Isuzu",
"model" : "NQR",
"payloadCapacity" : 7500.0
} ] ]
}
这个Json和前面有点区别,我们再和之前没有调用activateDefaultTyping方法的对比一下:
{
"vehicles" : [ {
"make" : "BMW",
"model" : "S500",
"seatingCapacity" : 5,
"topSpeed" : 250.0
}, {
"make" : "Isuzu",
"model" : "NQR",
"payloadCapacity" : 7500.0
} ]
}
细心的读者可能发现其中的区别:新的Json把原来的每个输出都变成了一个Array,并且第一个元素是类型信息,第二个才是真正的json。比如我们看下面的部分:
[ "com.github.fancyerii.learnjackson.a02.pojo.Car", {
"make" : "BMW",
"model" : "S500",
"seatingCapacity" : 5,
"topSpeed" : 250.0
} ]
原来的那个{}对象变成了一个数组[],数组的第一个元素是个字符串,值为对象的全限定名称,第二个元素才是真正对象转换的json。
有的读者可能觉得这样保存类型信息看起来很乱,因为这个json的结构和原来完全不同,如果是别的语言的客户端看到之后会很奇怪。我们可以修改activateDefaultTyping方法的第三个参数,把它从JsonTypeInfo.As.WRAPPER_ARRAY(默认值)改成JsonTypeInfo.As.PROPERTY,那么结果如下:
{
"@class" : "com.github.fancyerii.learnjackson.a02.pojo.Truck$Fleet",
"vehicles" : [ "java.util.ArrayList", [ {
"@class" : "com.github.fancyerii.learnjackson.a02.pojo.Car",
"make" : "BMW",
"model" : "S500",
"seatingCapacity" : 5,
"topSpeed" : 250.0
}, {
"@class" : "com.github.fancyerii.learnjackson.a02.pojo.Truck",
"make" : "Isuzu",
"model" : "NQR",
"payloadCapacity" : 7500.0
} ] ]
}
这样看起来是不是好多了?我们看到,它把某个对象的类型信息用一个特殊的@class属性来保存,这样整体JSON的结构就没有变化。但是注意:JsonTypeInfo.As.PROPERTY只会在对象里增加@class属性,如果是一个Array,它还是会用数组的第一个字段来保存类型信息。
接下来我们来看第一个参数,它的类型是PolymorphicTypeValidator,它是通过BasicPolymorphicTypeValidator的builder方法构造的:
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.github.fancyerii.learnjackson.a02.pojo")
.allowIfSubType("java.util.ArrayList")
.build();
它的意思是对于com.github.fancyerii.learnjackson.a02.pojo包下的所有类或者java.util.ArrayList(及其子类)都保存类的信息。因为我们通常只是对于那些有多态的类才需要增加类型信息,而且如果所有的类都尝试,可能也会有安全漏洞。所以我们通过PolymorphicTypeValidator来告诉ObjectMapper哪些类序列化的时候需要额外增加类型信息。
注意:如果ObjectMapper设置了PolymorphicTypeValidator,即使输入的JSON没有@class,也是不能序列化的,比如:
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.github.fancyerii.learnjackson.a02.pojo")
.build();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
MyBean myBean = new MyBean(1, "hi");
String json = objectMapper.writeValueAsString(myBean);
System.out.println(json);
MyBean myBean1 = objectMapper.readValue("\"intValue\":1,\"strValue\":\"hi\"}", MyBean.class);
我们配置了只能反序列化com.github.fancyerii.learnjackson.a02.pojo包,而我们的MyBean不是这个包下的,那么就不能反序列化。
最后是第二个参数,它告诉ObjectMapper哪些类序列化的时候需要输出类型信息。我们这里设置的是NON_FINAL,也就是非Final的类型。因为Final类型没有多态的问题,是没有必要保留类型信息的。注意:这里的final指的是我们定义的时候,在Fleet类里我们定义了private List
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TruckList {
private List<Truck> trucks;
}
public class TestDefaultTyping4 {
public static void main(String[] args) throws JsonProcessingException {
List<Truck> trucks = new ArrayList<>();
trucks.add(new Truck("Isuzu", "NQR", 7500.0));
TruckList truckList = new TruckList(trucks);
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.github.fancyerii.learnjackson.a02")
.allowIfSubType("java.util.ArrayList")
.build();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
ObjectWriter writer = objectMapper.writer(new DefaultPrettyPrinter());
String json = writer.writeValueAsString(truckList);
System.out.println(json);
TruckList truckList2 = objectMapper.readValue(json, TruckList.class);
json = writer.writeValueAsString(truckList2);
System.out.println(json);
}
}
我们看到输出为:
{
"@class" : "com.github.fancyerii.learnjackson.a02.TruckList",
"trucks" : [ "java.util.ArrayList", [ {
"make" : "Isuzu",
"model" : "NQR",
"payloadCapacity" : 7500.0
} ] ]
}
List是保存了其类型信息(java.util.ArrayList,但是里面的内容没有保存Truck的类型信息。
类级别的注解
前面的全局设置比较简单,但是我们发现它会把所有的非final类都假设类型信息。其实一般我们大部分类虽然不是final,但是通常也没有子类,增加那么多信息让JSON很冗余。为了解决这个问题,Jackson提供了类级别的注解,让我们只对某些类增加类型信息以实现多态的序列化和反序列化。
为了实现Vehicle及其子类的多态,我们需要修改Vehicle,增加一些注解:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "@type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Car.class, name = "car"),
@JsonSubTypes.Type(value = Truck.class, name = "truck")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
public abstract class Vehicle {
private String make;
private String model;
}
这里需要两个注解,第一个是JsonTypeInfo,它说明Vehicle及其子类在序列化的时候需要保存额外的类型信息。那怎么区分不同的子类呢?就是通过一个特别的名字(JsonTypeInfo.Id.NAME),并且把这个名字作为类的一个特殊属性存储,这个特殊属性的名字叫做”@type”。属性名字是任意的,为了不与正常的属性冲突,我们给它起名@type。
那么细心的读者可能会问题,不同的子类名字在哪里呢?这就是下面的注解的作用。JsonSubTypes为子类定义一个特殊的名字,这里把Car.class起名”car”,Truck.class起名”truck”。这里的起名也是任意的(当然不能重名,另外也需要好记)。
好了,接下来我们测试一下:
public class TestPolymorphicType2 {
public static void main(String[] args) throws JsonProcessingException {
List<Vehicle> vehicleList = new ArrayList<>();
vehicleList.add(new Car("BMW", "S500", 5, 250.0));
vehicleList.add(new Truck("Isuzu", "NQR", 7500.0));
Fleet fleet = new Fleet(vehicleList);
ObjectMapper objectMapper = new ObjectMapper();
ObjectWriter writer = objectMapper.writer(new DefaultPrettyPrinter());
String json = writer.writeValueAsString(fleet);
System.out.println(json);
Fleet fleet2 = objectMapper.readValue(json, Fleet.class);
json = writer.writeValueAsString(fleet2);
System.out.println(json);
}
}
我们看一下序列化后的结果:
{
"vehicles" : [ {
"@type" : "car",
"make" : "BMW",
"model" : "S500",
"seatingCapacity" : 5,
"topSpeed" : 250.0
}, {
"@type" : "truck",
"make" : "Isuzu",
"model" : "NQR",
"payloadCapacity" : 7500.0
} ]
}
我们看到,每个Vehicle的子类都增加了一个@type属性,对于Car.class,其值为”car”,对于Truck.class其值为”truck”。这样反序列化的时候Jackson就知道怎么处理了。
除了用名字来区分类,我们当然可以用类的全限定名称。我们可以设置如下注解:
@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "@class")
@Data
@NoArgsConstructor
@AllArgsConstructor
public abstract class Vehicle {
private String make;
private String model;
}
使用use = JsonTypeInfo.Id.CLASS后,就不需要再给类起名字了。不过这对于非Java的客户端来说可能不太友好,而且类名通常很长。下面是测试的结果:
{
"vehicles" : [ {
"@class" : "com.github.fancyerii.learnjackson.a02.pojo2.Car",
"make" : "BMW",
"model" : "S500",
"seatingCapacity" : 5,
"topSpeed" : 250.0
}, {
"@class" : "com.github.fancyerii.learnjackson.a02.pojo2.Truck",
"make" : "Isuzu",
"model" : "NQR",
"payloadCapacity" : 7500.0
} ]
}
不过使用CLASS有个好处,如果我们增加一个新的子类,什么也不用做。但是如果使用Name,如果不增加@JsonSubTypes.Type,则会出错。比如我们定义了Car的子类Trailor,如果不做任何处理,则输出为:
{
"vehicles" : [ {
"@type" : "car",
"make" : "BMW",
"model" : "S500",
"seatingCapacity" : 5,
"topSpeed" : 250.0
}, {
"@type" : "truck",
"make" : "Isuzu",
"model" : "NQR",
"payloadCapacity" : 7500.0
}, {
"@type" : "Trailer",
"make" : "BMW",
"model" : "T800",
"seatingCapacity" : 10,
"topSpeed" : 150.0,
"trailerType" : "a"
} ]
}
也就是没有定义名字的会默认使用类的名字(简短名字),在反序列化的时候Jackson找不到Trailer到底是哪个类,就会出现如下异常:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'Trailer' as a subtype of `com.github.fancyerii.learnjackson.a02.pojo2.Vehicle`: known type ids = [car, truck] (for POJO property 'vehicles')
也就是说,它不知道Trailer是哪个类,它只知道[car, truck]。
从子类里忽略一些字段
有的时候我们希望不要序列化/反序列父类的某些字段,我们可以通过如下几种方法实现。
注解
我们可以使用@JsonIgnore和@JsonIgnoreProperties两个注解来忽略某些字段。第一个是作用于类,而第二个作用于具体某个字段。我们通过例子来看它们的用法:
@Data
@NoArgsConstructor
@JsonIgnoreProperties({ "model", "seatingCapacity" })
public class Car extends Vehicle {
private int seatingCapacity;
@JsonIgnore
private double topSpeed;
public Car(String make, String model, int seatingCapacity, double topSpeed) {
super(make, model);
this.seatingCapacity = seatingCapacity;
this.topSpeed = topSpeed;
}
}
我们这里通过JsonIgnoreProperties忽略了集成来的”model”和它自己定义的seatingCapacity,同时通过JsonIgnore忽略了topSpeed。下面我们来看使用它的例子:
public class TestIgnore {
public static void main(String[] args) throws JsonProcessingException {
List<Vehicle> vehicleList = new ArrayList<>();
vehicleList.add(new Car("BMW", "S500", 5, 250.0));
vehicleList.add(new Truck("Isuzu", "NQR", 7500.0));
Fleet fleet = new Fleet(vehicleList);
ObjectMapper objectMapper = new ObjectMapper();
ObjectWriter writer = objectMapper.writer(new DefaultPrettyPrinter());
String json = writer.writeValueAsString(fleet);
System.out.println(json);
Fleet fleet2 = objectMapper.readValue(json, Fleet.class);
json = writer.writeValueAsString(fleet2);
System.out.println(json);
}
}
输出为:
{
"vehicles" : [ {
"@type" : "car",
"make" : "BMW"
}, {
"@type" : "truck",
"make" : "Isuzu",
"model" : "NQR",
"payloadCapacity" : 7500.0
} ]
}
我们看到,Car的实例只保存了make属性,而且反序列化的时候也不会有问题(不过其它字段都是空或者初始值)。
Mix-in
有的时候我们不想或者不能修改代码,比如某个类是第三方开发的。那么我们可以使用Mix-in的方法把这些注解混入到那个我们不能修改的类里面。我们通过一个例子来看看用法:
abstract class CarMixIn {
@JsonIgnore
public String make;
@JsonIgnore
public String topSpeed;
}
我们首先定义一个CarMixIn类,并且通过JsonIgnore忽略”make”和”topSpeed”两个字段,然后通过:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(Car.class, CarMixIn.class);
也就是把CarMixIn的代码混入(覆盖)进Car,从而使得Car的make和topSpeed被忽略。最终运行的结果如下:
{
"vehicles" : [ {
"@type" : "car",
"model" : "S500",
"seatingCapacity" : 5
}, {
"@type" : "truck",
"make" : "Isuzu",
"model" : "NQR",
"payloadCapacity" : 7500.0
} ]
}
我们看到Car只有model和seatingCapacity字段,而make和topSpeed被忽略掉了。
使用注解的内省(Introspection)
实现属性忽略的最强大的终极的方法是通过集成JacksonAnnotationIntrospector自己编写代码来实现复杂的逻辑。
public class IgnoranceIntrospector extends JacksonAnnotationIntrospector {
public boolean hasIgnoreMarker(AnnotatedMember m) {
return m.getDeclaringClass() == Vehicle.class && m.getName() == "model"
|| m.getDeclaringClass() == Car.class
|| m.getName() == "towingCapacity"
|| super.hasIgnoreMarker(m);
}
}
我们忽略掉了Vehicle的model字段,Car的全部字段,以及towingCapacity字段(不管它属于哪个类)。测试代码为:
public class TestAnnotationIntrospector {
public static void main(String[] args) throws JsonProcessingException {
List<Vehicle> vehicleList = new ArrayList<>();
vehicleList.add(new Car("BMW", "S500", 5, 250.0));
vehicleList.add(new Truck("Isuzu", "NQR", 7500.0));
Fleet fleet = new Fleet(vehicleList);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new IgnoranceIntrospector());
ObjectWriter writer = objectMapper.writer(new DefaultPrettyPrinter());
String json = writer.writeValueAsString(fleet);
System.out.println(json);
objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Fleet fleet2 = objectMapper.readValue(json, Fleet.class);
json = writer.writeValueAsString(fleet2);
System.out.println(json);
}
}
输出的JSON为:
{
"vehicles" : [ {
"@type" : "com.github.fancyerii.learnjackson.a02.pojo4.Car",
"make" : "BMW"
}, {
"@type" : "com.github.fancyerii.learnjackson.a02.pojo4.Truck",
"make" : "Isuzu",
"payloadCapacity" : 7500.0
} ]
}
但是我们注意:IgnoranceIntrospector会更改输出的JSON,但是反序列化的时候它还是按照完整的类来处理,所以会抛出异常。如果我们还是想正常反序列化,则我们需要重新构造一个ObjectMapper(因为setAnnotationIntrospector会改变很多反序列化的行为)。
子类处理的场景
子类之间的转换
为了演示,我们把Car和Truck类特有的字段忽略掉,这样就可以避免不兼容的字段:
@Data
@NoArgsConstructor
public class Car extends Vehicle {
@JsonIgnore
private int seatingCapacity;
@JsonIgnore
private double topSpeed;
public Car(String make, String model, int seatingCapacity, double topSpeed) {
super(make, model);
this.seatingCapacity = seatingCapacity;
this.topSpeed = topSpeed;
}
}
@Data
@NoArgsConstructor
public final class Truck extends Vehicle {
@JsonIgnore
private double payloadCapacity;
public Truck(String make, String model, double payloadCapacity) {
super(make, model);
this.payloadCapacity = payloadCapacity;
}
}
我们来测试一下:
public class TestSubTypeConversion {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Car car = new Car("Mercedes-Benz", "S500", 5, 250.0);
Truck truck = mapper.convertValue(car, Truck.class);
System.out.println(mapper.writeValueAsString(truck));
}
}
没有问题,不过注意:这里在Vehicle里不能定义多态的注解JsonTypeInfo,否则会出错。因为它会出错,因为它会检查@type和本身是否匹配。
没有构造函数
有的时候某个类没有空构造函数,那么反序列化就会失败,我们可以用@JsonCreator注解告诉Jackson用哪个构造函数构造对象:
@Data
public class Car extends Vehicle {
private int seatingCapacity;
private double topSpeed;
@JsonCreator
public Car(
@JsonProperty("make") String make,
@JsonProperty("model") String model,
@JsonProperty("seating") int seatingCapacity,
@JsonProperty("topSpeed") double topSpeed) {
super(make, model);
this.seatingCapacity = seatingCapacity;
this.topSpeed = topSpeed;
}
}
除了@JsonCreator,我们还得告诉Jackson非空构造函数的每一个参数对于JSON的哪个属性。有的读者可能奇怪,为什么Jackson不能自己对应上?因为函数的变量比如make在编译后默认是没有的(除非是调试版本)。所以我们需要显式的制定第一个参数对于Json的哪个属性。而且使用JsonProperty还有一个好处就是JSON属性名和参数可以不同。因为变量命名有一定的约束,比如不能以数字开始,但是JSON的属性并无此限制。所以如果JSON的属性名很诡异,我们也可以通过这种方法在Java里重命名一下。我们来测试一下:
public class TestNonEmptyConstructor {
public static void main(String[] args) throws JsonProcessingException {
List<Vehicle> vehicleList = new ArrayList<>();
vehicleList.add(new Car("BMW", "S500", 5, 250.0));
vehicleList.add(new Truck("Isuzu", "NQR", 7500.0));
Fleet fleet = new Fleet(vehicleList);
ObjectMapper objectMapper = new ObjectMapper();
ObjectWriter writer = objectMapper.writer(new DefaultPrettyPrinter());
String json = writer.writeValueAsString(fleet);
System.out.println(json);
Fleet fleet2 = objectMapper.readValue(json, Fleet.class);
json = writer.writeValueAsString(fleet2);
System.out.println(json);
}
}
能正常序列化和反序列化,没有问题!
- 显示Disqus评论(需要科学上网)