«1. Обзор

В этом кратком руководстве показано, как управлять сериализацией и десериализацией перечислений Java с помощью Jackson 2.

Чтобы копнуть немного глубже и узнать о других интересных вещах, которые мы можем сделать в Jackson 2, — вперед. к основному учебнику по Джексону.

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

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

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

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

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

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

1609.34

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

До версии Jackson 2.1.2 или если для перечисления требуется еще больше настроек, мы можем использовать собственный сериализатор Jackson. Во-первых, нам нужно определить его:

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 to Enum

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

public class City {
    
    private Distance distance;
    ...    
}

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

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

По умолчанию Джексон будет использовать имя Enum для десериализации из 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);
 
    ...
}

Используя эту аннотацию, мы просто говорим Джексону сопоставить значение @JsonProperty с объектом, аннотированным этим значением.

В результате приведенного выше объявления пример строки 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. Использование пользовательского десериализатора

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

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

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 в Enum для указать наш пользовательский десериализатор:

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

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

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

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

«В этой статье показано, как получить лучший контроль над процессами сериализации и десериализации и форматами перечислений Java.

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