Jackson - Can't deserialize datetime with timezone offset 'unparsed text found at index 23'
Asked Answered
M

1

10

My datetime has to come from frontend with timezone offset: 2017-07-04T06:00:00.000+01:00

I cannot deserialize it with Jackson. The error is:

Text '2017-07-04T06:00:00.000+01:00' could not be parsed, unparsed text found at index 23;

I was trying to Google the solution by all are about DateTime with Z at the end.

    @NotNull
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS aZ")
    private LocalDateTime time;

Is there any solution for that?

Materiel answered 4/7, 2017 at 20:25 Comment(5)
There is. You should probably give us the details about how the parsing was supposed to happen, maybe show us some code.Stubbs
added the code, sorryMateriel
With next to no Jackson experience I’m probably shooting blindfolded: your string seems to match an OffsetDateTime, so what happens if you change your type to that and leave out the pattern?Stubbs
Are Jackson patterns like DateTimeFormatter patterns? If so, should it be yyyy-MM-dd'T'HH:mm:ss.SSSZ? (no space, no a)Stubbs
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") private OffsetDateTime time; Still not workingMateriel
O
14

The pattern a is used to parse AM/PM, which is not in the input String, that's why you get a parse error.

The input format matches an OffsetDateTime, which can be parsed with the respective built-in formatter DateTimeFormatter.ISO_OFFSET_DATE_TIME, so you can use this formatter in a deserializer object and register it in the module. You must also remove the JsonFormat annotation from the field.

ObjectMapper om = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
LocalDateTimeDeserializer deserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
module.addDeserializer(LocalDateTime.class, deserializer);
om.registerModule(module);

This will parse the input and convert it to a LocalDateTime. In the test I've made, the value of LocalDateTime was set to 2017-07-04T06:00.


To control the output, you can either do:

om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Which will output the LocalDateTime as 2017-07-04T06:00:00, or you can use a custom formatter:

LocalDateTimeSerializer serializer = new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS a"));
module.addSerializer(LocalDateTime.class, serializer);

The serializer above will output the field as 2017-07-04T06:00:00.000 AM. Please note that the Z pattern will not work because a LocalDateTime has no timezone information and it can't resolve its offset - because when you deserialized to a LocalDateTime, the offset information in the input (+01:00) was lost.


Another alternative (without the need to configure the object mapper) is to use the correct pattern in the annotation:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS[xxx]")
private LocalDateTime time;

Note that I used the pattern [xxx] because the offset (+01:00) can be optional: when deserializing, this information is lost becase a LocalDateTime has no information about timezones and offsets, so when serializing this field won't be found - making the field optional (using [] delimiters) make it work for both deserialization and serialization.

This will deserialize the input 2017-07-04T06:00:00.000+01:00 and serialize to 2017-07-04T06:00:00.000 (note that the optional offset is not used in serialization, as the LocalDateTime has no such information).


If you want different formats for deserialization and serialization, you can also create custom classes and anotate them in the field:

public class CustomDeserializer extends LocalDateTimeDeserializer {
    public CustomDeserializer() {
        super(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    }
}

public class CustomSerializer extends LocalDateTimeSerializer {
    public CustomSerializer() {
        super(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS a"));
    }
}

// in this case, don't use @JsonFormat
@JsonSerialize(using = CustomSerializer.class)
@JsonDeserialize(using = CustomDeserializer.class)
private LocalDateTime time;

This will use the format 2017-07-04T06:00:00.000+01:00 for deserialize and the format 2017-07-04T06:00:00.000 AM to serialize.

Orchestrate answered 4/7, 2017 at 21:20 Comment(4)
would be nice if you could elaborate more how to really implement it as I am a little bit lost, but your solution looks good so I'd like to get know how to use itMateriel
The object mapper is used to serialize and deserialize. How are you doing the deserialization?Orchestrate
automatically with JacksonMateriel
so good. Made it in seperate file to make it reusable across whole project. Great!Materiel

© 2022 - 2024 — McMap. All rights reserved.