Джексон Дата

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

1. обзор

В этом руководстве мы будем сериализовать даты с помощью Джексона. Мы начнем с сериализации простого java.util.Date, затем Joda-Time, а также Java 8DateTime.

2. СериализоватьDate в метку времени

Во-первых, давайте посмотрим, как сериализовать простойjava.util.Date with Jackson.

В следующем примере мы сериализуем экземпляр «Event», который имеет полеDate «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 года по всемирному координированному времени).

Фактический результат сериализации «event»:

{
   "name":"party",
   "eventDate":3600000
}

3. СериализоватьDate в соответствии с ISO-8601

Сериализация в этом кратком формате временной метки не является оптимальной. Давайте теперь сериализуемDate в формат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. НастроитьObjectMapperDateFormat

В предыдущих решениях по-прежнему отсутствует полная гибкость выбора точного формата для представления экземпляровjava.util.Date.

Давайте теперь посмотрим на конфигурацию, которая позволит намset our formats for representing dates:

@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 для форматированияDate

Затем давайте посмотрим на аннотацию@JsonFormat кcontrol the date format on individual classes, а не глобально, для всего приложения:

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. Пользовательский сериализаторDate

Далее - чтобы получить полный контроль над выводом, мы воспользуемся настраиваемым сериализатором для дат:

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));
}

Дальнейшее чтение:

Как сериализовать и десериализовать Enums с Джексоном

Как сериализовать и десериализовать Enum как JSON-объект, используя Jackson 2.

Read more

Джексон - Custom Serializer

Управляйте выводом JSON с помощью Jackson 2 с помощью пользовательского сериализатора.

Read more

Начало работы с пользовательской десериализацией в Джексоне

Используйте Джексона, чтобы отобразить пользовательский JSON на любой граф объектов Java с полным контролем над процессом десериализации.

Read more

7. Сериализируйте Joda-Time с Джексоном

Даты не всегда являются экземпляромjava.util.Date; на самом деле - они все больше и больше представлены каким-то другим классом - и наиболее распространенным является, конечно же, реализацияDateTime из библиотеки Joda-Time.

Посмотрим, как мы можемserialize DateTime with 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. Сериализация JodaDateTime с помощью настраиваемого сериализатора

Если нам не нужна дополнительная зависимость Joda-Time Jackson - мы также можем использоватьa custom serializer (аналогично предыдущим примерам), чтобы получить чистую сериализацию экземпляров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 8Date с помощью Jackson

Далее - давайте посмотрим, как сериализовать Java 8DateTime - в этом примереLocalDateTime – using 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 8Date без дополнительных зависимостей

Если вам не нужна дополнительная зависимость, вы всегда можете использоватьa custom serializer to write out the Java 8 DateTime to 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. ДесериализоватьDate

Далее - давайте посмотрим, как десериализовать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. Десериализовать JodaZonedDateTime с сохранением часового пояса

В своей конфигурации по умолчанию Джексон подстраивает часовой пояс JodaZonedDateTime к часовому поясу локального контекста. Поскольку по умолчанию часовой пояс локального контекста не установлен и должен быть настроен вручную, Джексон настраивает часовой пояс на 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]

К счастью, есть быстрое и простое решение для этого странного поведения по умолчанию:we just have to tell Jackson, not to adjust the time zone.

Это можно сделать, добавив приведенную ниже строку кода в приведенный выше тестовый пример:

objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

Обратите внимание, что для сохранения часового пояса мы также должны отключить поведение по умолчанию при сериализации даты в метку времени.

13. Пользовательский десериализаторDate

Давайте также посмотрим, как использоватьa custom Date deserializer; мы напишем собственный десериализатор для свойства «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. Заключение

В этой обширной статьеDate мы рассмотрели наиболее подходящие способы использованияJackson can help marshalli and unmarshall a date to JSON разумного формата, который мы контролируем.

Реализация всех этих примеров и фрагментов кодаcan be found in my GitHub project - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.