Рубрики
Без рубрики

Как сериализовать и десериализовать перечисления с помощью Джексона

Как сериализовать и десериализовать перечисление в качестве объекта JSON с помощью Jackson 2.

Автор оригинала: Eugen Paraschiv.

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

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

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

Это приведет к желаемому результату при сериализации этого перечисления для Расстояния. МИЛЯ:

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

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

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

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

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

1609.34

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

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

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 в перечисление

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

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

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

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

По умолчанию Джексон будет использовать имя перечисления для десериализации из JSON .

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

{"distance":"KILOMETER"}

На Расстояние.КИЛОМЕТР объект:

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

4.2. Использование @JsonValue

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

Во — первых, давайте использовать @JsonValue с одним из методов getter – getMeters() :

public enum Distance {
    ...

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

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

{"distance":"0.0254"}

Джексон будет искать объект перечисления, который имеет getMeters() возвращаемое значение 0.0254. В этом случае объектом является Расстояние. ДЮЙМ:

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"}

Будет сопоставлено с расстоянием .КИЛОМЕТР объект:

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

4.4. Использование @JsonCreator

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

Рассмотрим представление JSON:

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

Теперь давайте определим метод forValues() factory с помощью аннотации @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, или мы можем использовать более старую версию Джексона, которая не поддерживает одну или несколько аннотаций, описанных до сих пор.

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

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

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

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

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

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

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