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 上获取。