Перейти к основному содержимому

Сериализуйте и десериализуйте логические значения как целые числа с Джексоном

· 6 мин. чтения

1. Введение

Библиотека Джексона является стандартом де-факто в мире Java, когда речь идет об обработке JSON. Несмотря на четко определенные значения по умолчанию Джексона, для сопоставления логического значения с Integer нам по-прежнему необходимо выполнять ручную настройку.

Конечно, некоторые разработчики задаются вопросом, как добиться этого наилучшим образом и с минимальными усилиями.

В этой статье мы объясним, как сериализовать логические значения в виде целых чисел плюс числовые строки — и наоборот в Джексоне.

2. Сериализация

Сначала мы рассмотрим часть сериализации. Чтобы протестировать сериализацию Boolean to Integer , давайте определим нашу модель Game :

public class Game {

private Long id;
private String name;
private Boolean paused;
private Boolean over;

// constructors, getters and setters
}

Как обычно, сериализация объекта Game по умолчанию будет использовать ObjectMapper Джексона :

ObjectMapper mapper = new ObjectMapper();
Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);

Неудивительно, что вывод для логических полей будет по умолчанию — true или false :

{"id":1, "name":"My Game", "paused":true, "over":false}

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

{"id":1, "name":"My Game", "paused":1, "over":0}

2.1. Конфигурация уровня поля

Один довольно простой способ сериализации в Integer — аннотировать наши логические поля с помощью @JsonFormat и установить для него Shape.NUMBER :

@JsonFormat(shape = Shape.NUMBER)
private Boolean paused;

@JsonFormat(shape = Shape.NUMBER)
private Boolean over;

Затем давайте попробуем нашу сериализацию в тестовом методе:

ObjectMapper mapper = new ObjectMapper();
Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);

assertThat(json)
.isEqualTo("{\"id\":1,\"name\":\"My Game\",\"paused\":1,\"over\":0}");

Как мы заметили в нашем выводе JSON, наши логические поля — paused и over — сформировались в числа 1 и 0 . Мы видим, что значения имеют целочисленный формат, поскольку они не заключены в кавычки.

2.2. Глобальная конфигурация

Иногда аннотировать каждое поле нецелесообразно. Например, в зависимости от требований нам может потребоваться глобальная настройка сериализации Boolean to Integer .

К счастью, Джексон позволяет нам глобально настроить @JsonFormat , переопределив значения по умолчанию в ObjectMapper :

ObjectMapper mapper = new ObjectMapper();
mapper.configOverride(Boolean.class)
.setFormat(JsonFormat.Value.forShape(Shape.NUMBER));

Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);

assertThat(json)
.isEqualTo("{\"id\":1,\"name\":\"My Game\",\"paused\":1,\"over\":0}");

3. Десериализация

Точно так же мы можем также захотеть получить логические значения из чисел при десериализации строк JSON в наши модели.

К счастью, по умолчанию Джексон может анализировать числа — только 1 и 0 — в логические значения. Таким образом, нам не нужно использовать аннотацию @JsonFormat или какую-либо дополнительную настройку.

Таким образом, без настройки, давайте посмотрим на это поведение с помощью другого метода тестирования:

ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":1,\"name\":\"My Game\",\"paused\":1,\"over\":0}";
Game game = mapper.readValue(json, Game.class);

assertThat(game.isPaused()).isEqualTo(true);
assertThat(game.isOver()).isEqualTo(false);

Следовательно, десериализация целых чисел в логические значения поддерживается в Jackson .

4. Числовые строки вместо целых чисел

Другой вариант использования — использование числовых строк — «1» и «0» — вместо целых чисел. В этом случае для сериализации логических значений в числовые строки или десериализации их обратно в логические значения потребуются дополнительные усилия.

4.1. Сериализация в числовые строки

Чтобы сериализовать логическое значение в числовые строковые эквиваленты, нам нужно определить собственный сериализатор.

Итак, давайте создадим наш NumericBooleanSerializer , расширив JsonSerializer Джексона :

public class NumericBooleanSerializer extends JsonSerializer<Boolean> {

@Override
public void serialize(Boolean value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value ? "1" : "0");
}
}

В качестве примечания: обычно логические типы могут иметь значение null . Однако Джексон обрабатывает это внутри и не принимает во внимание наш пользовательский сериализатор, когда поле значения равно null . Поэтому здесь мы в безопасности.

Далее мы зарегистрируем наш пользовательский сериализатор, чтобы Джексон распознал и использовал его.

Если нам нужно такое поведение только для ограниченного числа полей, мы можем выбрать конфигурацию уровня поля с аннотацией @JsonSerialize .

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

@JsonSerialize(using = NumericBooleanSerializer.class)
private Boolean paused;

@JsonSerialize(using = NumericBooleanSerializer.class)
private Boolean over;

Затем аналогичным образом пробуем сериализацию в тестовом методе:

ObjectMapper mapper = new ObjectMapper();
Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);

assertThat(json)
.isEqualTo("{\"id\":1,\"name\":\"My Game\",\"paused\":\"1\",\"over\":\"0\"}");

Хотя реализация тестового метода практически идентична предыдущим, следует обратить внимание на кавычки — «приостановлено»: «1», «завершено»: «0» — вокруг числовых значений. Конечно, это указывает на то, что эти значения являются реальными строками, содержащими числовое содержимое.

И последнее, но не менее важное: на случай, если нам нужно выполнить эту пользовательскую сериализацию везде, Джексон поддерживает глобальную настройку сериализаторов, добавляя их в ObjectMapper через модули Джексона :

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Boolean.class, new NumericBooleanSerializer());
mapper.registerModule(module);

Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);

assertThat(json)
.isEqualTo("{\"id\":1,\"name\":\"My Game\",\"paused\":\"1\",\"over\":\"0\"}");

В результате Джексон сериализует все поля с логическими типами как числовые строки, если мы используем один и тот же экземпляр ObjectMapper .

4.2. Десериализация из числовых строк

Подобно сериализации, на этот раз мы определим пользовательский десериализатор для преобразования числовых строк в логические значения.

Давайте создадим наш класс NumericBooleanDeserializer , расширив JsonDeserializer :

public class NumericBooleanDeserializer extends JsonDeserializer<Boolean> {

@Override
public Boolean deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
if ("1".equals(p.getText())) {
return Boolean.TRUE;
}
if ("0".equals(p.getText())) {
return Boolean.FALSE;
}
return null;
}

}

Затем мы еще раз аннотируем наши логические поля, но на этот раз с помощью @JsonDeserialize :

@JsonSerialize(using = NumericBooleanSerializer.class)
@JsonDeserialize(using = NumericBooleanDeserializer.class)
private Boolean paused;

@JsonSerialize(using = NumericBooleanSerializer.class)
@JsonDeserialize(using = NumericBooleanDeserializer.class)
private Boolean over;

Итак, давайте напишем еще один тестовый метод, чтобы увидеть наш NumericBooleanDeserializer в действии:

ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":1,\"name\":\"My Game\",\"paused\":\"1\",\"over\":\"0\"}";
Game game = mapper.readValue(json, Game.class);

assertThat(game.isPaused()).isEqualTo(true);
assertThat(game.isOver()).isEqualTo(false);

В качестве альтернативы, глобальная конфигурация нашего пользовательского десериализатора также возможна через модули Джексона:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Boolean.class, new NumericBooleanDeserializer());
mapper.registerModule(module);

String json = "{\"id\":1,\"name\":\"My Game\",\"paused\":\"1\",\"over\":\"0\"}";
Game game = mapper.readValue(json, Game.class);

assertThat(game.isPaused()).isEqualTo(true);
assertThat(game.isOver()).isEqualTo(false);

5. Вывод

В этой статье мы описали, как сериализовать логические значения в целые числа и числовые строки и как их десериализовать обратно.

Как всегда, исходный код примеров и многое другое доступно на GitHub .