1. Обзор
В этом уроке мы сериализуем даты с Джексоном. Мы начнем с сериализации простого файла java.util. Date
, затем Joda-Time и, наконец, Java 8 DateTime
.
2. Сериализация даты
в метку времени
Во-первых, давайте посмотрим, как сериализовать простой java.util.Date
с помощью Jackson .
В следующем примере мы сериализуем экземпляр « Event
», который имеет поле даты
« eventDate
»:
@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).
Фактический результат сериализации « event
»:
{
"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.Date .
И наоборот, давайте посмотрим на конфигурацию, которая позволит нам установить наши форматы для представления дат :
@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<Date> {
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. Сериализировать Joda-Time с Джексоном
Даты не всегда являются экземпляром java.util.Date.
На самом деле все больше и больше даты представлены каким-то другим классом, и распространенной является реализация DateTime
из библиотеки Joda-Time.
Давайте посмотрим, как мы можем сериализовать DateTime
с помощью Jackson .
Мы будем использовать модуль jackson-datatype-joda
для готовой поддержки Joda-Time: ``
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
<version>2.9.7</version>
</dependency>
Затем мы можем просто зарегистрировать 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 DateTime
с помощью пользовательского сериализатора
Если нам не нужна дополнительная зависимость Joda-Time Jackson, мы также можем использовать собственный сериализатор (аналогичный предыдущим примерам) для чистой сериализации экземпляров DateTime :
public class CustomDateTimeSerializer extends StdSerializer<DateTime> {
private static DateTimeFormatter formatter =
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");
public CustomDateTimeSerializer() {
this(null);
}
public CustomDateTimeSerializer(Class<DateTime> 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
: ``
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.7</version>
</dependency>
Затем все, что нам нужно сделать, это зарегистрировать 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 в JSON
:
public class CustomLocalDateTimeSerializer
extends StdSerializer<LocalDateTime> {
private static DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
public CustomLocalDateTimeSerializer() {
this(null);
}
public CustomLocalDateTimeSerializer(Class<LocalDateTime> 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. Десериализовать дату
Теперь давайте посмотрим, как мы можем десериализовать Date
с помощью Jackson . В следующем примере мы десериализуем экземпляр 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. Пользовательский десериализатор даты
Мы также можем использовать собственный десериализатор даты .
Мы напишем собственный десериализатор для свойства « eventDate
»:
public class CustomDateDeserializer extends StdDeserializer<Date> {
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. Исправление исключения
InvalidDefinition
``
При создании экземпляра 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
.
Строковое представление даты отличается от объекта типа LocalDate
в памяти, поэтому нам нужен внешний десериализатор для чтения этого поля из String
и сериализатор для преобразования даты в формат
String .
``
Эти методы также применяются к LocalDateTime,
единственное изменение заключается в использовании эквивалентного класса для LocalDateTime
.
14.1. Джексон зависимость
Джексон позволяет нам исправить это несколькими способами. Во-первых, мы должны убедиться, что зависимость jsr310
есть в нашем pom.xml
:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.11.0</version>
</dependency>
14.2. Сериализация к одному объекту даты
Чтобы иметь возможность обрабатывать LocalDate
, нам нужно зарегистрировать 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
и JsonFormat
на уровне объекта:
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
, он гораздо более настраиваемый.
15. Заключение
В этой обширной статье о датах мы рассмотрели несколько способов, которыми Джексон может помочь маршалировать и демаршалировать дату в JSON ,
используя разумный формат, который мы можем контролировать. ****
Как всегда, код примеров можно найти на GitHub .