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 .