1. 概述
本文我们将演示如何使用Jackson 2控制Java枚举的序列化与反序列化行为。
想要学习更多关于Jackson 2的知识,请访问我们的Jackson系列教程。
2. 枚举定义
首先我们的枚举定义如下:
public enum Distance {
KILOMETER("km", 1000),
MILE("miles", 1609.34),
METER("meters", 1),
INCH("inches", 0.0254),
CENTIMETER("cm", 0.01),
MILLIMETER("mm", 0.001);
private String unit;
private final double meters;
private Distance(String unit, double meters) {
this.unit = unit;
this.meters = meters;
}
// standard getters and setters
}
3.枚举序列化为JSON
3.1. 默认序列化行为
默认情况下,Jackson 会将Java枚举序列化为String字符串,例如:
new ObjectMapper().writeValueAsString(Distance.MILE);
输出结果为枚举名称:
"MILE"
而我们希望将枚举转为JSON对象,像下面这样:
{"unit":"miles","meters":1609.34}
3.2. 枚举转为JSON对象
从Jackson 2.1.2开始,我们可以通过 @JsonFormat 注解来实现:
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Distance { ... }
Distance.MILE 序列化结果为:
{"unit":"miles","meters":1609.34}
3.3. @JsonValue 注解
另外,我们可以在getter上添加@JsonValue注解,来控制枚举序列化结果:
public enum Distance {
...
@JsonValue
public String getMeters() {
return meters;
}
}
@JsonValue 注解的字段,就是我们最终序列化的值:
1609.34
3.4. 自定义序列化器
在 Jackson 2.1.2 之前,如果需要实现更多自定义行为,我们可以自定义Jackson serializer:
public class DistanceSerializer extends StdSerializer {
public DistanceSerializer() {
super(Distance.class);
}
public DistanceSerializer(Class t) {
super(t);
}
public void serialize(
Distance distance, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeString(distance.name());
generator.writeFieldName("unit");
generator.writeString(distance.getUnit());
generator.writeFieldName("meters");
generator.writeNumber(distance.getMeters());
generator.writeEndObject();
}
}
然后通过 @JsonSerialize 注解应用
@JsonSerialize(using = DistanceSerializer.class)
public enum TypeEnum { ... }
最终序列化结果:
{"name":"MILE","unit":"miles","meters":1609.34}
4. 反序列化 JSON 为枚举
首先,我们定义一个具有 Distance 成员变量的 City 类:
public class City {
private Distance distance;
...
}
接下来,我们讨论将 JSON 字符串反序列化为枚举的几种不同的方法。
4.1. 默认行为
默认情况下,Jackson 使用枚举名称来反序列化。
例如,我们的JSON数据如下:
{"distance":"KILOMETER"}
distance 字段被序列化为 Distance.KILOMETER 对象:
City city = new ObjectMapper().readValue(json, City.class);
assertEquals(Distance.KILOMETER, city.getDistance());
Jackson 在解析枚举的时候,默认是区分大小写的,如果希望不区分,需要设置ACCEPT_CASE_INSENSITIVE_ENUMS参数。 例如,下面的JSON:
{"distance":"KiLoMeTeR"}
设置枚举不区分大小写:
ObjectMapper objectMapper = JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.build();
City city = objectMapper.readValue(json, City.class);
assertEquals(Distance.KILOMETER, city.getDistance());
4.2. 使用 @JsonValue 注解
我们已经学习了如何使用 @JsonValue 来序列化枚举。我们也可以使用同样的注解进行反序列化。这是因为枚举值是常量。
首先,让我们在一个getter方法上使用 @JsonValue 注解——*getMeters()*:
public enum Distance {
...
@JsonValue
public double getMeters() {
return meters;
}
}
现在,getMeters() 方法的返回值代表枚举对象。因此,当反序列化示例JSON时:
{"distance":"0.0254"}
Jackson 会寻找 getMeters() 返回值为 0.0254 的枚举对象。本例为 Distance.INCH:
assertEquals(Distance.INCH, city.getDistance());
4.3. 使用 @JsonProperty
@JsonProperty 注解用于枚举实例:
public enum Distance {
@JsonProperty("distance-in-km")
KILOMETER("km", 1000),
@JsonProperty("distance-in-miles")
MILE("miles", 1609.34);
...
}
通过使用这个注解,我们告诉Jackson将 @JsonProperty 的值映射到带有此值注解的对象。
{"distance": "distance-in-km"}
将被映射到 Distance.KILOMETER 对象:
assertEquals(Distance.KILOMETER, city.getDistance());
4.4. 使用 @JsonCreator
Jackson 会调用有 @JsonCreator 注解的方法来获取实例。
例如下面的 JSON:
{
"distance": {
"unit":"miles",
"meters":1609.34
}
}
我们定义名为 forValues() 的工厂方法,并加上 @JsonCreator 注解:
public enum Distance {
@JsonCreator
public static Distance forValues(@JsonProperty("unit") String unit,
@JsonProperty("meters") double meters) {
for (Distance distance : Distance.values()) {
if (
distance.unit.equals(unit) && Double.compare(distance.meters, meters) == 0) {
return distance;
}
}
return null;
}
...
}
注:使用 @JsonProperty 注解将JSON字段与方法参数绑定
然后测试反序列化结果:
assertEquals(Distance.MILE, city.getDistance());
4.5. 自定义反序列化器
当我们可能无法访问修改枚举的源码,或者使用较老的Jackson版本不支持上述注解时,我们还可以通过自定义反序列化器实现。
参考我们的之前的自定义反序列化文章,我们将首先需要创建反序列化类:
public class CustomEnumDeserializer extends StdDeserializer<Distance> {
@Override
public Distance deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
String unit = node.get("unit").asText();
double meters = node.get("meters").asDouble();
for (Distance distance : Distance.values()) {
if (distance.getUnit().equals(unit) && Double.compare(
distance.getMeters(), meters) == 0) {
return distance;
}
}
return null;
}
}
然后使用 @JsonDeserialize 注解在枚举上指定我们的自定义反序列化器:
@JsonDeserialize(using = CustomEnumDeserializer.class)
public enum Distance {
...
}
测试结果
assertEquals(Distance.MILE, city.getDistance());
5. 总结
本文演示了如何更好地控制Java枚举的序列化和反序列化过程以及格式。
文中代码可从 GitHub 上获取。