How to make milliseconds optional in @JsonFormat for Timestamp parsing with Jackson?
Asked Answered
M

5

14

I have the following declaration in my object:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX")
private ZonedDateTime start;

And when I parse timestamps like 2016-12-08T12:16:07.124Z (using Jackson Json De-serilaizer) it works fine, but once I receive timestamps without milliseconds (such as "2016-12-08T12:16:07Z"), it throws exception.

How I can possibly make milliseconds optional in format specification?

Maestricht answered 8/12, 2016 at 10:32 Comment(0)
G
20

If you are using Java 8 Try specifying .SSS inside square brackets [.SSS]

JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSS]X")
Gelb answered 8/12, 2016 at 10:52 Comment(2)
Does not work on Java 8 version 102. The brackets are also not mentioned in the JavaDoc: docs.oracle.com/javase/8/docs/api/java/text/…Thermonuclear
[.SSS] works only for a 3 digit millisecond values. if its one digit or 2 digit it breaks. E.g. 2019-03-24T16:14:08.23 will need "yyyy-MM-dd'T'HH:mm:ss[.SS] patternAndros
P
15

If millis consist of 1 or 2 or 3 digit you can use this pattern

JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSS][.SS][.S]X")

Optional section order strict

Philbo answered 5/3, 2020 at 14:10 Comment(0)
L
4

For those of you that were not able to get the [.SSS] solution to work, here is what I ended up doing.

Retain the @JsonFormat annotation on your field for serialization, but build a custom deserializer for parsing Dates which might not have the milliseconds portion specified. Once you implement the deserializer you will have to register it with your ObjectMapper as a SimpleModule

class DateDeserializer extends StdDeserializer<Date> {

    private static final SimpleDateFormat withMillis = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
    private static final SimpleDateFormat withoutMillis = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");

    public DateDeserializer() {
        this(null);
    }

    public DateDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String dateString = p.getText();
        if (dateString.isEmpty()) {
            //handle empty strings however you want,
            //but I am setting the Date objects null
            return null;
        }

        try {
            return withMillis.parse(dateString);
        } catch (ParseException e) {
            try {
                return withoutMillis.parse(dateString);
            } catch (ParseException e1) {
                throw new RuntimeException("Unable to parse date", e1);
            }
        }
    }
}

Now that you have a custom deserializer, all that is left is to register it. I am doing so with a ContextResolver<ObjectMapper> that I already had in my project, but however you work with your ObjectMapper should be fine.

@Provider
class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {

    private final ObjectMapper mapper;

    public ObjectMapperContextResolver() {
        mapper = new ObjectMapper();
        SimpleModule dateModule = new SimpleModule();
        dateModule.addDeserializer(Date.class, new DateDeserializer());
        mapper.registerModule(dateModule);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return mapper;
    }
}
Lazaro answered 19/3, 2018 at 20:35 Comment(1)
Very useful information on how to register a custom ObjectMapper in a JEE application.Halland
T
0

Whether you can specify optional format components with square brackets depends on the deserializer in use. SimpleDateFormat does not recognize it. But if you register jsr310 module in fasterxml (possibly through JavaTimeModule) and deserialize java.time classes, then it is possible.

How? It uses DateTimeFormatter which has the following documentation included:

   [       optional section start
   ]       optional section end
Tingaling answered 12/6 at 10:13 Comment(0)
S
0

Just to extend on @hitman1000's answer. In the case of precision down to microseconds level, do as follows:

@JsonFormat(
        shape = Shape.STRING,
        pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSSSSS][.SSSSS][.SSSS][.SSS][.SS][.S]X")

Once again, the order of those optional sections is important! I believe it has something to do with how the syntax is parsed.

Spindell answered 14/8 at 11:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.