10

We're migrating from Joda to Java Time. Currently we use DateTime of Joda in our entity. AFAIK DateTime is equivalent to two types in Java: OffsetDateTime and ZonedDateTime. Since we're going to persist them in DB, we're gonna use OffsetDateTime (any comment on this?).

Now the problem is how to configure Jackson's ObjectMapper properly. All examples I found on the web are about local types for which Jackson's already provided de/serializer implementations (e.g. LocalDateTime, LocalDateTimeSerializer and LocalDateTimeDeserializer).

I finally managed to do something like this:

public class OffsetDateTimeSerializer extends StdSerializer<OffsetDateTime> {

    private final DateTimeFormatter formatter; // We need custom format!

    public OffsetDateTimeSerializer(DateTimeFormatter formatter) {
        super(OffsetDateTime.class);
        this.formatter = formatter;
    }

    @Override
    public void serialize(OffsetDateTime value, JsonGenerator generator, SerializerProvider provider) throws IOException {
        generator.writeString(value.format(formatter));
    }

}

and

public class OffsetDateTimeDeserializer extends StdDeserializer<OffsetDateTime> {

    private final DateTimeFormatter formatter; // We need custom format!

    public OffsetDateTimeDeserializer(DateTimeFormatter formatter) {
        super(OffsetDateTime.class);
        this.formatter = formatter;
    }

    @Override
    public OffsetDateTime deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
        return OffsetDateTime.parse(parser.readValueAs(String.class), formatter);
    }

}

Now my question is what is the best way to configure Jackson's ObjectMapper to de/serialize Java 8 date-time values?

UPDATE: the accepted answer does not really solve my problem (read the discussion in comments). I ended up with a little simpler code than what I proposed in the above. See my own answer as well.

Rad
  • 4,292
  • 8
  • 33
  • 71
  • 3
    You don't need to write that yourself, there is already a Jackson module for the Java 8 date and time API: https://github.com/FasterXML/jackson-modules-java8 – Jesper Apr 23 '18 at 11:42
  • Updated the question. How to use custom format? – Rad Apr 23 '18 at 12:25
  • 1
    related to my question: https://stackoverflow.com/questions/37166217/localdatetime-deserialization-with-localdatetime-parse – Andrew Tobilko Apr 23 '18 at 12:30
  • I guess so. Our format is `2016-05-11T17:32:20.897+0000` or `2016-05-11T17:32:20.897+00:00` (No Zulu notation) (we want to support multiple formats for inputs) – Rad Apr 23 '18 at 12:35

4 Answers4

17

You don't need to write your custom serializer and deserializer for JSR-310 types. Jackson has a custom module to handle that and will provide you with the serializer and deserializer you need.

First add the jackson-datatype-jsr310 artifact to your dependencies:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9</version>
</dependency>

Then register the JavaTimeModule module in your ObjectMapper:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Most JSR-310 types will be serialized using a standard ISO-8601 string representation. If you need a custom format, you can use your own serializer and deserializer implementation.

See the documentation for details.

cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • Thanks for the answer. But then how to use custom format? – Rad Apr 23 '18 at 12:24
  • @Rad Most JSR-310 types will be serialized using a standard ISO-8601 string representation. If you need a custom format, you can use your own serializer and deserializer implementation. – cassiomolin Apr 23 '18 at 13:23
  • So the implementation I posted is the only way to go? – Rad Apr 23 '18 at 13:24
  • @Rad Yes. That's the way to go if you want a custom format. – cassiomolin Apr 23 '18 at 13:46
  • 3
    Beware the microseconds part of ISO-8601. We've had issues with timestamps missing the .SSS part when serializing Instants where it happens to be .000. If that's a problem for your clients, you'll want to customize the serialization to use a custom pattern that doesn't do this: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'". – Jilles van Gurp Apr 23 '18 at 13:56
  • 1
    @CassioMazzochiMolin would you please take a look at my own answer https://stackoverflow.com/a/50018489/2194119. – Rad Apr 25 '18 at 09:21
6

Ok, so I ended up with the following (a little less code, no concrete classes):

private JavaTimeModule newJavaTimeModule() {
    JavaTimeModule module = new JavaTimeModule();
    module.addSerializer(LocalDate.class, new LocalDateSerializer(DEFAULT_LOCAL_DATE_FORMATTER));
    module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DEFAULT_LOCAL_DATE_FORMATTER));
    module.addSerializer(OffsetDateTime.class, offsetDateTimeSerializer());
    module.addDeserializer(OffsetDateTime.class, offsetDateTimeDeserializer());

    return module;
}

private StdSerializer<OffsetDateTime> offsetDateTimeSerializer(DateTimeFormatter formatter) {
    return new OffsetDateTimeSerializer(OffsetDateTimeSerializer.INSTANCE, false, formatter) {};
}

private StdDeserializer<OffsetDateTime> offsetDateTimeDeserializer(DateTimeFormatter formatter) {
    return new InstantDeserializer<OffsetDateTime>(InstantDeserializer.OFFSET_DATE_TIME, formatter) {};
}
Rad
  • 4,292
  • 8
  • 33
  • 71
1

You can check this answer, that contains a lot of information about how to use java.time classes and custom formats: https://stackoverflow.com/a/46263957

To parse both "+00:00" and "+0000", you can use a DateTimeFormatterBuilder with optional sections:

DateTimeFormatter f = new DateTimeFormatterBuilder()
    // date and time fields
    .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    // optional offset in format hh:mm
    .optionalStart()
    .appendOffset("+HH:MM", "+00:00")
    .optionalEnd()
    // optional offset in format hhmm
    .optionalStart()
    .appendOffset("+HHMM", "+0000")
    .optionalEnd()
    .toFormatter();
crownd2
  • 11
  • 1
0

Spring boot does dependency management for Jackson. Upon 2.6, the jsr310 module can be managed automatically, so you just use Jackson 2.6+ and the module is added, which is available in com.fasterxml.jackson.datatype:jackson-datatype-jsr310.

If you have access to the ObjectMapper, such as in a unit test, you can register the JavaTimeModule . If you don't, like what happens when the JSON comes in as @RequestBody, you must configure in application.properties:

spring.jackson.serialization.write-dates-as-timestamps=false

And specify the date format in JSON like:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXXX")
private OffsetDateTime transactionDateTime;

And the string will be parsed correctly. The format uses the letters specified in SimpleDateFormat.

Mind the number of X at the end; varied length of which represents distinct forms of zone offset. Read Java API SimpleDateFormat part for more information.

WesternGun
  • 11,303
  • 6
  • 88
  • 157