Найти тему

Как сериализовать и десериализовать перечисления с использованием Jackson

Оглавление

1. Обзор

В этом быстром руководстве мы узнаем, как управлять процессом сериализации и десериализации Java Enum с помощью 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 Представление перечислений по умолчанию

По умолчанию Jackson представляет перечисления Java как простые строки. Например:

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

Приведет к следующему результату:

"MILE"

However, when marshaling this Enum to a JSON Object, we would like to get something like:

{"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 Пользовательский сериализатор для перечисления

Если мы используем версию 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 в перечисление

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

public class City {

private Distance distance;
...
}

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

4.1 Стандартное поведение

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

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

{"distance":"KILOMETER"}

В объект Distance.KILOMETER:

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

Если мы хотим, чтобы Jackson осуществлял десериализацию из JSON без учета регистра по имени перечисления, нам нужно настроить ObjectMapper для включения функции 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());

Как показано в тесте выше, мы включаем функцию ACCEPT_CASE_INSENSITIVE_ENUMS с помощью построителя JsonMapper.

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

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

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

public enum Distance {
...

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

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

{"distance":"0.0254"}

Jackson будет искать объект Enum, у которого возвращаемое значение метода getMeters() равно 0.0254. В данном случае это объект Distance.INCH:

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

4.3 Использование аннотации @JsonProperty

The @JsonProperty annotation is used on enumeration instances:

public enum Distance {
@JsonProperty("distance-in-km")
KILOMETER("km", 1000),
@JsonProperty("distance-in-miles")
MILE("miles", 1609.34);

...
}

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

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

{"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 с аргументами метода.

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

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

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

Мы можем использовать пользовательский десериализатор, если ни один из описанных выше методов не подходит. Например, у нас может не быть доступа к исходному коду перечисления, или мы можем использовать более старую версию 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 для перечисления, чтобы указать наш пользовательский десериализатор:

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

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

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

5. Завершение

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

Оригинал статьи: https://www.baeldung.com/jackson-serialize-enums