Java 8 DateTime serialization and Jackson JSR 310

7.4k Views Asked by At

I am using Java 8 DateTime and Jackson jsr 310 support in a Spring Boot application.

I disabled SerializationFeature.WRITE_DATES_AS_TIMESTAMPS to force Jackson to serialize the localDatetime to string instead of int array.

But I found a weird format problem when the datetime microsecond or nonaseconds are end of 0. I think the serialized result could be equals the date.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), but it is not, the format method omitted the zeros.

The complete the example test codes.

private LocalDateTime date;

private OffsetDateTime offsetDate;

private ZonedDateTime zonedDate;

@Before
public void setup() throws ServletException {
    date = LocalDateTime.of(2015, 8, 15, 11, 40, 10, 100_000_000);
    offsetDate = OffsetDateTime.of(2015, 8, 15, 11, 40, 10, 100_000_000, ZoneOffset.ofHours(8));
    zonedDate = ZonedDateTime.of(2015, 8, 15, 11, 40, 10, 100_000_000, ZoneId.of("Asia/Shanghai"));
}

@Test
public void testDateFormat() throws Exception {
    Map<String, Object> map = new HashMap<>();
    map.put("localDate", date);
    map.put("offsetDate", offsetDate);
    map.put("zonedDate", zonedDate);

    String json = objectMapper.writeValueAsString(map);

    log.debug("converted json result @" + json);

    JsonNode rootNode = objectMapper.readTree(json);

    JsonNode localDateNode = rootNode.get("localDate");
    assertEquals("local date should be equals", date.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), localDateNode.textValue());

    JsonNode offsetDateNode = rootNode.get("offsetDate");
    assertEquals("offsetDate date should be equals", offsetDate.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), offsetDateNode.textValue());

    JsonNode zonedDateNode = rootNode.get("zonedDate");
    assertEquals("zonedDate date should be equals", zonedDate.format(DateTimeFormatter.ISO_ZONED_DATE_TIME), zonedDateNode.textValue());

}

The test failed, and the log debug prints

{
    "zonedDate":"2015-08-15T11:40:10.100+08:00[Asia/Shanghai]",
    "localDate":"2015-08-15T11:40:10.100",
    "offsetDate":"2015-08-15T11:40:10.1+08:00"
}

zonedDate and localDate microseconds are end with 00, but offsetDate is not. And date.format also get different result which micrseconds is NOT end with 00.

The print result of format, toString, the json text

LocalDateTime format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)@2015-08-15T11:40:10.1
LocalDateTime toString                                     @2015-08-15T11:40:10.100
LocalDateTime serialized json node text                    @2015-08-15T11:40:10.100
OffsetDateTime format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)@2015-08-15T11:40:10.1+08:00
OffsetDateTime toString                                      @2015-08-15T11:40:10.100+08:00
OffsetDateTime serialized json node text                     @2015-08-15T11:40:10.1+08:00
ZonedDateTime format(DateTimeFormatter.ISO_ZONED_DATE_TIME)@2015-08-15T11:40:10.1+08:00[Asia/Shanghai]
ZonedDateTime toString                                     @2015-08-15T11:40:10.100+08:00[Asia/Shanghai]
ZonedDateTime serialized json node text                    @2015-08-15T11:40:10.100+08:00[Asia/Shanghai]
  1. It seems the toString result should be the desired result in Jackson serialization, in above logging, OffsetDateTime json node microseconds text should be end with 00 .
  2. Why the format method result omits the ending 00?

The complete sample codes can be found from my github.com.

angular-springmvc-sample-boot

ISODateTest

3

There are 3 best solutions below

0
On

Here is what I did for configuring the ObjectMapper.

@Configuration
public class JacksonConfiguration {

  @Bean
  public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
    ObjectMapper objectMapper = builder.build();
    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    objectMapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
    return objectMapper;
  }
}
0
On

As a workaround, I add my own serializer:

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());
    SimpleModule module = new SimpleModule();
    module.addSerializer(ZonedDateTime.class, new JsonSerializer<ZonedDateTime>() {
        @Override
        public void serialize(ZonedDateTime zonedDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeString(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZZZ").format(zonedDateTime));
        }
    });
    objectMapper.registerModule(module);
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
3
On

Your test has 2 problems:

  1. You're comparing apples to oranges, ObjectMapper serialized result and DateTimeFormatter formatted result. Each has their own defaults and configuration options and it's no surprise that the results are different.
  2. 100_000_000 is really 1and according to ISO_LOCAL_TIME definition, DateTimeFormatter prints "One to nine digits for the nano-of-second. As many digits will be output as required.". In your case, only 1 digit is required and that's exactly what you got in the output for LocalDateTime.

I'm not entirely clear what you're trying to do here and what is the purpose of the tests that you wrote. If you're expecting 3 digits of nanosecond precision from your formatter, you've to roll your own. Depending on toString formatting in your code is a bad idea.