Рубрики
Без рубрики

Свидание с Джексоном

Как обрабатывать даты с помощью Джексона – сортировка и удаление их из JSON и обратно.

Автор оригинала: baeldung.

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 .