ObjectMapper configuation for Java 8 Time API
Asked Answered
H

4

12

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.

Horal answered 23/4, 2018 at 10:8 Comment(4)
You don't need to write that yourself, there is already a Jackson module for the Java 8 date and time API: github.com/FasterXML/jackson-modules-java8Kismet
Updated the question. How to use custom format?Horal
related to my question: #37166717Kelula
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)Horal
T
18

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.

Tubercular answered 23/4, 2018 at 11:36 Comment(6)
Thanks for the answer. But then how to use custom format?Horal
@Horal 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.Tubercular
So the implementation I posted is the only way to go?Horal
@Horal Yes. That's the way to go if you want a custom format.Tubercular
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'".Strobilaceous
@CassioMazzochiMolin would you please take a look at my own answer https://mcmap.net/q/913475/-objectmapper-configuation-for-java-8-time-api.Horal
H
7

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(DEFAULT_DATE_TIME_FORMATTER));
    module.addDeserializer(OffsetDateTime.class, offsetDateTimeDeserializer(DEFAULT_DATE_TIME_FORMATTER));

    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) {};
}
Horal answered 25/4, 2018 at 9:20 Comment(0)
C
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();
Casilde answered 23/4, 2018 at 13:27 Comment(0)
C
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.

Cicerone answered 29/1, 2019 at 16:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.