Джексон неправильно сериализует ZonedDateTime в Spring Boot

У меня есть простое приложение с Spring Boot and Jetty. У меня есть простой контроллер, возвращающий объект с Java 8 ZonedDateTime:

public class Device {
  // ...
  private ZonedDateTime lastUpdated;

  public Device(String id, ZonedDateTime lastUpdated, int course, double latitude, double longitude) {
    // ...
    this.lastUpdated = lastUpdated;
    // ...
  }

  public ZonedDateTime getLastUpdated() {
    return lastUpdated;
  }
}

В моем RestController я просто:

@RequestMapping("/devices/")
public @ResponseBody List<Device> index() {
  List<Device> devices = new ArrayList<>();
  devices.add(new Device("321421521", ZonedDateTime.now(), 0, 39.89011333, 24.438176666));

  return devices;
}

Я ожидал, что формат ZonedDateTime будет отформатирован в соответствии с ISO-форматом, но вместо этого я получаю весь дамп JSON класса следующим образом:

"lastUpdated":{"offset":{"totalSeconds":7200,"id":"+02:00","rules":{"fixedOffset":true,"transitionRules":[],"transitions":[]}},"zone":{"id":"Europe/Berlin","rules":{"fixedOffset":false,"transitionRules":[{"month":"MARCH","timeDefinition":"UTC","standardOffset":{"totalSeconds":3600,"id":"+01:00","rules":{"fixedOffset":true,"transitionRules":[],"transitions":[]}},"offsetBefore":{"totalSeconds":3600,"id":"+01:00","rules":{"fixedOffset":true,"transitionRules":[],"transitions":[]}},"offsetAfter":{"totalSeconds":7200,"id":"+02:00", ...

У меня просто есть приложение spring-boot-starter-web, используя spring-boot-starter-jetty и исключая spring-boot-starter-tomcat.

Почему Джексон ведет себя так в Spring Boot?

** ОБНОВЛЕНИЕ **

Для тех, кто ищет полное пошаговое руководство, как это решить, я нашел это, задав вопрос: http://lewandowski.io/2016/02/formatting-java-time-with-spring-boot-using-json/

Ответы

Ответ 1

Существует библиотека jackson-datatype-jsr310. Попробуйте.

Эта библиотека охватывает новый API datetime и включает сериализаторы для ZonedDateTime тоже.

Все, что вам нужно, это просто добавить JavaTimeModule:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());

ОБНОВЛЕНИЕ

Чтобы преобразовать datetime в ISO-8601 string, вы должны отключить WRITE_DATES_AS_TIMESTAMPS. Вы можете легко выполнить либо переопределение ObjectMapper bean, либо с помощью свойств приложения:

spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false

Ответ 2

Если вы либо не полагаетесь на функцию автоматической настройки SpringBoot, вы не предоставляете spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false свойство в свой файл конфигурации - или по какой-либо причине вы создаете экземпляр ObjectMapper вручную. Вы можете отключить эту функцию программно следующим образом:

ObjectMapper m = new ObjectMapper();
m.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

это для 2.8.7

Ответ 3

Ответ уже упоминался выше, но я думаю, что ему не хватает информации. Для тех, кто хочет анализировать временные метки Java 8 во многих формах (не только ZonedDateTime). Вам нужна последняя версия jackson-datatype-jsr310 в вашем POM и зарегистрирован следующий модуль:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

Чтобы проверить этот код

@Test
void testSeliarization() throws IOException {
    String expectedJson = "{\"parseDate\":\"2018-12-04T18:47:38.927Z\"}";
    MyPojo pojo = new MyPojo(ZonedDateTime.parse("2018-12-04T18:47:38.927Z"));

    // serialization
    assertThat(objectMapper.writeValueAsString(pojo)).isEqualTo(expectedJson);

    // deserialization
    assertThat(objectMapper.readValue(expectedJson, MyPojo.class)).isEqualTo(pojo);
}

Обратите внимание, что вы можете настроить свой сопоставитель объектов по всему миру spring или dropwizard для достижения этого. Я еще не нашел чистого способа сделать это как аннотацию на поле без регистрации пользовательского (де) сериализатора.