20

I'm using Jackson 2.8 and need to communicate with an API that doesn't allow milliseconds within ISO 8601 timestamps.

The expected format is this: "2016-12-24T00:00:00Z"

I'm using Jackson's JavaTimeModule with WRITE_DATES_AS_TIMESTAMPS set to false.

But this will print milliseconds.

So I tried to use objectMapper.setDateFormat which didn't change anything.

My current workaround is this:

ObjectMapper om = new ObjectMapper();

DateTimeFormatter dtf = new DateTimeFormatterBuilder()
    .appendInstant(0)
    .toFormatter();

JavaTimeModule jtm = new JavaTimeModule();
jtm.addSerializer(Instant.class, new JsonSerializer<Instant>() {
    @Override
    public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
        gen.writeString(dtf.format(value));
    }
});

om.registerModule(jtm);

I'm overriding the default serializer for Instant.class which works.


Is there any nice way using some configuration parameter to solve this?

Benjamin M
  • 23,599
  • 32
  • 121
  • 201
  • 1
    have you tried `DateTimeFormatter.ISO_INSTANT` as a format? https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_INSTANT – Gelunox Nov 08 '16 at 13:05
  • The target API is out of your control? – Michael-O Nov 08 '16 at 13:09
  • 1
    Yes, tried that. It will also print Milliseconds. Maybe it works if I explicitly create an `Instant` with Milliseconds set to `0`. But that's not safe enough for my use case. It must **always** ignore/throw away the Milliseconds for serialization. `... EDIT:` yes, the API is from some external company. no control. – Benjamin M Nov 08 '16 at 13:10
  • This is pretty much the reverse but same topic: https://stackoverflow.com/questions/45276807/iso8601-with-milliseconds-in-json-using-jackson – Christophe Roussy Jul 24 '17 at 12:26

3 Answers3

13

Update:

Just add a @JsonFormat annotation with the date format above the Instant property. It's very easy.

In the case you have an ObjectMapper with the JavaTimeModule like next:

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

If you have a class with an Instant property, you should add the @JsonFormat annotation and put the date pattern which hasn't milliseconds. It would be like next:

public static class TestDate {

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss", timezone = "UTC")
    Instant instant;

    //getters & setters
}

So if you serialize an object to Json it works perfectly:

String json = mapper.writeValueAsString(testDate);
System.out.println(json); 

Output

{"instant":"2016-11-10 06:03:06"}


Old Answer. I don't know why but It doesn't work properly:

You could use the Jackson2ObjectMapperBuilder to build it.

You just need to add the dateFormat you want. It would be something like next:

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

ObjectMapper mapper = Jackson2ObjectMapperBuilder
            .json()       
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) 
            .modules(new JavaTimeModule())
            .dateFormat(dateFormat)
            .build();
Benjamin M
  • 23,599
  • 32
  • 121
  • 201
Pau
  • 14,917
  • 14
  • 67
  • 94
  • Copy & Pasted your code. It doesn't work. Still get date + time + milliseconds. :( – Benjamin M Nov 09 '16 at 09:18
  • 1
    Thank you. But this way I have to annotate all and every Instant field.. I'd like to set that format globally within the ObjectMapper. – Benjamin M Nov 10 '16 at 10:11
  • 2
    It should be "yyyy-MM-dd HH:mm:ss" for a 24-hour format to avoid confusion (a.m. and p.m. are not part of this output) – RobertG Dec 10 '18 at 17:40
6

Here is an alternative which is something you can set globally, but will need you to use ZonedDateTime with instant formatter as we can't set the format for the Instant Serializer provided with Java Time Module.

You wont see any side effects of using zoned date time for instant as jackson serializes the zone id separately and is disabled by default. So technically, this is similar to applying the formatter to Instant.

When used this way, the ZonedDateTime serializer delegates the serialization to InstantBaseSerializer and uses the specified custom format.

@RunWith(JUnit4.class)
public class InstantNoMillisTest {

    private ObjectMapper objectMapper;

    @Before
    public void init() {
        JavaTimeModule module = new JavaTimeModule();
        ZonedDateTimeSerializer zonedDateTimeSerializer = new ZonedDateTimeSerializer(new DateTimeFormatterBuilder().appendInstant(0).toFormatter());
        module.addSerializer(ZonedDateTime.class, zonedDateTimeSerializer);
        module.addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME);

        objectMapper = Jackson2ObjectMapperBuilder.json()
                .modules(module)
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .build();
    }

    @Test
    public void serialize() throws IOException {
        ZonedDateTime zonedDateTime = ZonedDateTime.now();
        String noMillis = objectMapper.writeValueAsString(zonedDateTime);
        System.out.print(noMillis);
    }

    @Test
    public void deserialize() throws IOException {
        String dateTime = "\"2017-10-26T12:54:59Z\"";
        ZonedDateTime noMillis = objectMapper.readValue(dateTime, ZonedDateTime.class);
        System.out.print(noMillis);
    }
}
André Gasser
  • 1,065
  • 2
  • 14
  • 34
s7vr
  • 73,656
  • 11
  • 106
  • 127
6

Here's some Kotlin code of formatting Instant fields, so they will not contain milliseconds, you can use custom date formatters

ObjectMapper().apply {
        val javaTimeModule = JavaTimeModule()
        javaTimeModule.addSerializer(Instant::class.java, Iso8601WithoutMillisInstantSerializer())
        registerModule(javaTimeModule)
        disable(WRITE_DATES_AS_TIMESTAMPS)
    }

private class Iso8601WithoutMillisInstantSerializer
        : InstantSerializer(InstantSerializer.INSTANCE, false, DateTimeFormatterBuilder().appendInstant(0).toFormatter())
ZZ 5
  • 1,744
  • 26
  • 41