1. Обзор
В этом уроке мы будем сериализовать даты с Джексоном. Мы начнем с сериализации простого java.util. Дата , затем Joda-Время, а также Java 8 DateTime .
2. Сериализация даты в метку времени
Во – первых, давайте посмотрим, как сериализовать простой java.util.Свидание с Джексоном .
В следующем примере – мы сериализуем экземпляр ” Событие “, который имеет Дату поле ” Дата события “:
@Test public void whenSerializingDateWithJackson_thenSerializedToTimestamp() throws JsonProcessingException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm"); df.setTimeZone(TimeZone.getTimeZone("UTC")); Date date = df.parse("01-01-1970 01:00"); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); mapper.writeValueAsString(event); }
Здесь важно то, что Джексон по умолчанию сериализует данные в формат временной метки (количество миллисекунд с 1 января 1970 года по UTC).
Фактическим результатом сериализации ” событие ” является:
{ "name":"party", "eventDate":3600000 }
3. Сериализовать дату в соответствии с ISO-8601
Сериализация в этом кратком формате временных меток не является оптимальной. Теперь давайте сериализуем Данные в формат ISO-8601 :
@Test public void whenSerializingDateToISO8601_thenSerializedToText() throws JsonProcessingException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm"); df.setTimeZone(TimeZone.getTimeZone("UTC")); String toParse = "01-01-1970 02:30"; Date date = df.parse(toParse); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // StdDateFormat is ISO8601 since jackson 2.9 mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true)); String result = mapper.writeValueAsString(event); assertThat(result, containsString("1970-01-01T02:30:00.000+00:00")); }
Обратите внимание, что представление даты теперь гораздо более читабельно.
4. Настройте формат даты ObjectMapper
Предыдущим решениям по-прежнему не хватает полной гибкости в выборе точного формата для представления java.util.Дата экземпляры.
Давайте теперь рассмотрим конфигурацию, которая позволит нам установить наши форматы для представления дат :
@Test public void whenSettingObjectMapperDateFormat_thenCorrect() throws JsonProcessingException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm"); String toParse = "20-12-2014 02:30"; Date date = df.parse(toParse); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(df); String result = mapper.writeValueAsString(event); assertThat(result, containsString(toParse)); }
Обратите внимание, что, хотя теперь мы стали более гибкими в отношении формата даты, мы по – прежнему используем глобальную конфигурацию на уровне всего ObjectMapper .
5. Используйте @JsonFormat для форматирования даты
Далее давайте рассмотрим аннотацию @JsonFormat для управления форматом даты в отдельных классах вместо глобального для всего приложения:
public class Event { public String name; @JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") public Date eventDate; }
А теперь – давайте проверим это:
@Test public void whenUsingJsonFormatAnnotationToFormatDate_thenCorrect() throws JsonProcessingException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); df.setTimeZone(TimeZone.getTimeZone("UTC")); String toParse = "20-12-2014 02:30:00"; Date date = df.parse(toParse); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); String result = mapper.writeValueAsString(event); assertThat(result, containsString(toParse)); }
6. Пользовательский сериализатор данных
Далее – чтобы получить полный контроль над выводом, мы используем пользовательский сериализатор для дат:
public class CustomDateSerializer extends StdSerializer{ private SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); public CustomDateSerializer() { this(null); } public CustomDateSerializer(Class t) { super(t); } @Override public void serialize (Date value, JsonGenerator gen, SerializerProvider arg2) throws IOException, JsonProcessingException { gen.writeString(formatter.format(value)); } }
Далее – давайте использовать его в качестве сериализатора нашего поля ” EventDate ” .:
public class Event { public String name; @JsonSerialize(using = CustomDateSerializer.class) public Date eventDate; }
Наконец – давайте проверим это:
@Test public void whenUsingCustomDateSerializer_thenCorrect() throws JsonProcessingException, ParseException { SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); String toParse = "20-12-2014 02:30:00"; Date date = df.parse(toParse); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); String result = mapper.writeValueAsString(event); assertThat(result, containsString(toParse)); }
Дальнейшее чтение:
Как сериализовать и десериализовать перечисления с помощью Джексона
Джексон – Пользовательский сериализатор
Начало работы с пользовательской десериализацией в Джексоне
7. Сериализация Времени Джоды С Джексоном
Даты не всегда являются экземпляром java.util.Дата ; на самом деле – они все больше и больше представлены каким – то другим классом-и общим из них, конечно же, является реализация DateTime из библиотеки времени Joda.
Давайте посмотрим, как мы можем сериализовать DateTime с помощью Jackson .
Мы будем использовать модуль jackson-datatype-joda для встроенной поддержки Joda-Time:
com.fasterxml.jackson.datatype jackson-datatype-joda 2.9.7
А теперь мы можем просто зарегистрировать JodaModule и все готово:
@Test public void whenSerializingJodaTime_thenCorrect() throws JsonProcessingException { DateTime date = new DateTime(2014, 12, 20, 2, 30, DateTimeZone.forID("Europe/London")); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JodaModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); String result = mapper.writeValueAsString(date); assertThat(result, containsString("2014-12-20T02:30:00.000Z")); }
8. Сериализация Даты И Времени Joda С Помощью Пользовательского Сериализатора
Если нам не нужна дополнительная зависимость Joda-Time Jackson, мы также можем использовать пользовательский сериализатор (аналогично предыдущим примерам), чтобы получить DateTime экземпляры, сериализованные чисто:
public class CustomDateTimeSerializer extends StdSerializer{ private static DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"); public CustomDateTimeSerializer() { this(null); } public CustomDateTimeSerializer(Class t) { super(t); } @Override public void serialize (DateTime value, JsonGenerator gen, SerializerProvider arg2) throws IOException, JsonProcessingException { gen.writeString(formatter.print(value)); } }
Далее – давайте использовать его как наше свойство ” EventDate ” сериализатор:
public class Event { public String name; @JsonSerialize(using = CustomDateTimeSerializer.class) public DateTime eventDate; }
Наконец – давайте соберем все вместе и проверим это:
@Test public void whenSerializingJodaTimeWithJackson_thenCorrect() throws JsonProcessingException { DateTime date = new DateTime(2014, 12, 20, 2, 30); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); String result = mapper.writeValueAsString(event); assertThat(result, containsString("2014-12-20 02:30")); }
9. Сериализация Даты Java 8 С Джексоном
Далее – давайте посмотрим, как сериализовать Java 8 DateTime – в этом примере LocalDateTime – с помощью Jackson . Мы можем использовать модуль jackson-datatype-jsr310 :
com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.9.7
Теперь все, что нам нужно сделать, это зарегистрировать JavaTimeModule ( JSR310Module устарел), а Джексон позаботится обо всем остальном:
@Test public void whenSerializingJava8Date_thenCorrect() throws JsonProcessingException { LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); String result = mapper.writeValueAsString(date); assertThat(result, containsString("2014-12-20T02:30")); }
10. Сериализация Даты Java 8 Без Какой-Либо Дополнительной Зависимости
Если вам не нужна дополнительная зависимость, вы всегда можете использовать пользовательский сериализатор для записи Java 8 DateTime в JSON :
public class CustomLocalDateTimeSerializer extends StdSerializer{ private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); public CustomLocalDateTimeSerializer() { this(null); } public CustomLocalDateTimeSerializer(Class t) { super(t); } @Override public void serialize( LocalDateTime value, JsonGenerator gen, SerializerProvider arg2) throws IOException, JsonProcessingException { gen.writeString(formatter.format(value)); } }
Далее – давайте используем сериализатор для нашего поля ” EventDate ” .:
public class Event { public String name; @JsonSerialize(using = CustomLocalDateTimeSerializer.class) public LocalDateTime eventDate; }
А теперь – давайте проверим это:
@Test public void whenSerializingJava8DateWithCustomSerializer_thenCorrect() throws JsonProcessingException { LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30); Event event = new Event("party", date); ObjectMapper mapper = new ObjectMapper(); String result = mapper.writeValueAsString(event); assertThat(result, containsString("2014-12-20 02:30")); }
11. Десериализация Данных
Далее – давайте посмотрим, как десериализовать Дату с Джексоном . В следующем примере – мы десериализуем экземпляр ” Event “, содержащий дату:
@Test public void whenDeserializingDateWithJackson_thenCorrect() throws JsonProcessingException, IOException { String json = "{"name":"party","eventDate":"20-12-2014 02:30:00"}"; SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(df); Event event = mapper.readerFor(Event.class).readValue(json); assertEquals("20-12-2014 02:30:00", df.format(event.eventDate)); }
12. Десериализация Joda ZonedDateTime С Сохранением Часового Пояса
В конфигурации по умолчанию Джексон настраивает часовой пояс Joda ZonedDateTime в соответствии с часовым поясом локального контекста. Поскольку по умолчанию часовой пояс локального контекста не задан и должен быть настроен вручную, Джексон настраивает часовой пояс на GMT:
@Test public void whenDeserialisingZonedDateTimeWithDefaults_thenNotCorrect() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.findAndRegisterModules(); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Europe/Berlin")); String converted = objectMapper.writeValueAsString(now); ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class); System.out.println("serialized: " + now); System.out.println("restored: " + restored); assertThat(now, is(restored)); }
Этот тестовый случай завершится неудачей с выводом:
serialized: 2017-08-14T13:52:22.071+02:00[Europe/Berlin] restored: 2017-08-14T11:52:22.071Z[UTC]
К счастью, есть быстрое и простое исправление этого странного поведения по умолчанию: мы просто должны сказать Джексону, чтобы он не менял часовой пояс.
Это можно сделать, добавив нижеприведенную строку кода в приведенный выше тестовый случай:
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
Обратите внимание, что для сохранения часового пояса мы также должны отключить поведение по умолчанию при сериализации данных в метку времени.
13. Пользовательский десериализатор Данных
Давайте также посмотрим, как использовать пользовательский Data десериализатор ; мы напишем пользовательский десериализатор для свойства ” EventDate “:
public class CustomDateDeserializer extends StdDeserializer{ private SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); public CustomDateDeserializer() { this(null); } public CustomDateDeserializer(Class> vc) { super(vc); } @Override public Date deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException, JsonProcessingException { String date = jsonparser.getText(); try { return formatter.parse(date); } catch (ParseException e) { throw new RuntimeException(e); } } }
Далее – давайте использовать его в качестве десериализатора ” EventDate “.:
public class Event { public String name; @JsonDeserialize(using = CustomDateDeserializer.class) public Date eventDate; }
И, наконец, – давайте проверим это:
@Test public void whenDeserializingDateUsingCustomDeserializer_thenCorrect() throws JsonProcessingException, IOException { String json = "{"name":"party","eventDate":"20-12-2014 02:30:00"}"; SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss"); ObjectMapper mapper = new ObjectMapper(); Event event = mapper.readerFor(Event.class).readValue(json); assertEquals("20-12-2014 02:30:00", df.format(event.eventDate)); }
14. Исправление исключения InvalidDefinitionException
При создании экземпляра LocalDate мы можем столкнуться с исключением:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.LocalDate`(no Creators, like default construct, exist): no String-argument constructor/factory method to deserialize from String value ('2014-12-20') at [Source: (String)"2014-12-20"; line: 1, column: 1]
Эта проблема возникает из-за того, что JSON изначально не имеет формата даты, поэтому представляет даты в виде String .
Представление String даты не совпадает с объектом типа LocalDate в памяти, поэтому нам нужен внешний десериализатор для чтения этого поля из String и сериализатор для отображения даты в формат String .
Эти методы также применимы к LocalDateTime — единственное изменение заключается в использовании эквивалентного класса для LocalDateTime .
14.1. Зависимость Джексона
Джексон позволяет нам исправить это несколькими способами. Во-первых, мы должны убедиться, что jsr 310 зависимость находится в вашем pom.xml :
com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.11.0
14.2. Сериализация в объект с одной датой
Чтобы иметь возможность обрабатывать Локальные данные , нам нужно зарегистрировать JavaTimeModule с помощью нашего ObjectMapper .
Мы также отключаем функцию WRITE_DATES_AS_TIMESTAMPS в ObjectMapper , чтобы Джексон не добавлял цифры времени в вывод JSON:
@Test public void whenSerializingJava8DateAndReadingValue_thenCorrect() throws IOException { String stringDate = "\"2014-12-20\""; ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); LocalDate result = mapper.readValue(stringDate, LocalDate.class); assertThat(result.toString(), containsString("2014-12-20")); }
Здесь мы использовали встроенную поддержку Джексона для сериализации и десериализации дат.
14.3. Аннотации в POJO
Другой способ справиться с этой проблемой-использовать LocalDateDeserializer и Формат Json аннотации на уровне сущности:
public class EventWithLocalDate { @JsonDeserialize(using = LocalDateDeserializer.class) @JsonSerialize(using = LocalDateSerializer.class) @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy") public LocalDate eventDate; }
Аннотация @JsonDeserialize используется для указания пользовательского десериализатора, который разархивирует объект JSON. Аналогично, @JsonSerialize указывает пользовательский сериализатор для использования при маршалировании сущности.
Кроме того, аннотация @JsonFormat позволяет нам указать формат, в котором мы будем сериализовать значения дат. Таким образом, этот POJO можно использовать для чтения и записи JSON:
@Test public void whenSerializingJava8DateAndReadingFromEntity_thenCorrect() throws IOException { String json = "{\"name\":\"party\",\"eventDate\":\"20-12-2014\"}"; ObjectMapper mapper = new ObjectMapper(); EventWithLocalDate result = mapper.readValue(json, EventWithLocalDate.class); assertThat(result.getEventDate().toString(), containsString("2014-12-20")); }
Хотя этот подход требует больше работы, чем использование JavaTimeModule defaults, он гораздо более настраиваем.
15. Заключение
В этой обширной статье Data мы рассмотрели несколько способов Джексон может помочь маршалу и отменить дату в JSON , используя разумный формат, который мы контролируем.
Как всегда, примеры кода можно найти на GitHub .