Как сериализовать перечисления как объекты JSON с Джексоном

Как сериализовать и десериализовать Enums с Джексоном

1. обзор

Это краткое руководство покажет, как управлять способомJava Enums are serialized and deserialized with Jackson 2.

Чтобы копнуть немного глубже и изучитьother cool things we can do Jackson 2, перейдите кthe main Jackson tutorial.

2. Управление представлением Enum

Давайте определим следующее перечисление:

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. Представление перечисления по умолчанию

По умолчанию Джексон будет представлять Java Enums в виде простой строки - например:

new ObjectMapper().writeValueAsString(Distance.MILE);

Приведет к:

"MILE"

При маршалинге этогоEnum to a JSON Object мы хотели бы получить что-то вроде:

{"unit":"miles","meters":1609.34}

3.2. Enum как объект JSON

Начиная с Jackson 2.1.2, теперь есть опция конфигурации, которая может обрабатывать такое представление. Это можно сделать с помощью аннотации@JsonFormat на уровне класса:

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Distance { ... }

Это приведет к желаемому результату при сериализации этогоenum дляDistance.MILE:

{"unit":"miles","meters":1609.34}

3.3. Перечисления и@JsonValue

Еще один простой способ управления выводом маршалинга для перечисления - использовать аннотацию@JsonValue в геттере:

public enum Distance {
    ...

    @JsonValue
    public String getMeters() {
        return meters;
    }
}

Мы выражаем здесь то, чтоgetMeters() является фактическим представлением этого перечисления. Итак, результат сериализации будет:

1609.34

3.4. Пользовательский сериализатор для Enum

До версии Jackson 2.1.2 или, если для перечисления требуется еще большая настройка, мы можем использоватьcustom 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(using = DistanceSerializer.class)
public enum TypeEnum { ... }

Что приводит к:

{"name":"MILE","unit":"miles","meters":1609.34}

4. Десериализация JSON в Enum

Во-первых, давайте определим классCity, который имеет членDistance:

public class City {

    private Distance distance;
    ...
}

Далее мы обсудим различные способы десериализации строки JSON в Enum.

4.1. Поведение по умолчанию

By default, Jackson will use the Enum name to deserialize from JSON.

Например, он десериализует JSON:

{"distance":"KILOMETER"}

К объектуDistance.KILOMETER:

City city = new ObjectMapper().readValue(json, City.class);
assertEquals(Distance.KILOMETER, city.getDistance());

4.2. Используя@JsonValue

Мы узнали, как использовать@JsonValue для сериализации перечислений. Мы можем использовать ту же аннотацию и для десериализации. Это возможно, потому что значения Enum являются константами.

Сначала давайте воспользуемся@JsonValue с одним из методов получения -getMeters():

public enum Distance {
    ...

    @JsonValue
    public double getMeters() {
        return meters;
    }
}

Теперь возвращаемое значение методаgetMeters() представляет объекты Enum. Таким образом, при десериализации образца JSON:

{"distance":"0.0254"}

Джексон будет искать объект Enum, возвращаемое значение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);

    ...
}

Используя эту аннотацию,we are simply telling Jackson to map the value of the @JsonProperty to the object annotated with this value.

В результате вышеупомянутого объявления, пример строки JSON:

{"distance": "distance-in-km"}

Будет отображено на объектDistance.KILOMETER:

assertEquals(Distance.KILOMETER, city.getDistance());

4.4. Используя@JsonCreator

Джексон вызывает методы, помеченные@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 с аргументами метода.

Затем, когда мы десериализуем образец JSON, мы получим результат:

assertEquals(Distance.MILE, city.getDistance());

4.5. Использование пользовательскогоDeserializer

Пользовательский десериализатор может использоваться, если ни один из описанных методов не доступен. Например, у нас может не быть доступа к исходному коду Enum, или мы можем использовать старую версию Jackson, которая не поддерживает одну или несколько аннотаций, рассмотренных до сих пор.

Согласноour custom deserialization article, чтобы десериализовать JSON, предоставленный в предыдущем разделе, мы начнем с создания класса десериализации:

public class CustomEnumDeserializer extends StdDeserializer {

    @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 в Enum, чтобы указать наш настраиваемый десериализатор:

@JsonDeserialize(using = CustomEnumDeserializer.class)
public enum Distance {
   ...
}

И наш результат:

assertEquals(Distance.MILE, city.getDistance());

5. Заключение

В этой статье показано, как лучше контролироватьserialization and deserialization processes and formats of Java Enums.

Реализацию всех этих примеров и фрагментов кода можно найти вover on Github.