question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Custom `LocalDate` deserializer not being used with 2.11, jsr-310 datatype module

See original GitHub issue

Dear Jackson team,

It seems that as of Jackson 2.11 my custom (date) deserializer is no longer being used by the object mapper. I’ve tried various ways of registering the deserializer but none seem to work. In the previous version (2.10.4) this was not a problem.

Strangely the JSR310 LocalDateDeserializer is being detected and registered automatically from the classpath, skipping the deserializer in my custom module.

Example:

public class CustomLocalDateDeserializerTest {

    @Test
    public void parse_dates_shouldSucceed() throws JsonProcessingException {
        SimpleModule module = new SimpleModule();
        module.addDeserializer(LocalDate.class, new CustomLocalDateDeserializer());

        ObjectMapper objectMapper = JsonMapper.builder()
            .addModule(module)
            .build();

        final String json = "{\"endDate\":1567202400000,\"startDate\":1564610400000}";

        Dates dates = objectMapper.readValue(json, Dates.class);
        assertNotNull(dates);
        assertEquals(LocalDate.of(2019, 8, 1), dates.getStartDate());
        assertEquals(LocalDate.of(2019, 8, 31), dates.getEndDate());
    }

    @Getter
    @Setter
    public static class Dates {

        private LocalDate startDate;
        private LocalDate endDate;

    }

}

@Slf4j
public final class CustomLocalDateDeserializer extends com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer {

    public CustomLocalDateDeserializer() {
        super(DateTimeFormatter.ISO_LOCAL_DATE);
    }

    @Override
    public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
            long value = parser.getLongValue();
            if (!isValidEpochDay(value)) {
                return toLocalDate(value);
            }
        }
        return super.deserialize(parser, context);
    }

    private static boolean isValidEpochDay(long value) {
        try {
            ChronoField.EPOCH_DAY.checkValidValue(value);
        } catch (DateTimeException ex) {
            log.trace("Date value {} is not a valid epoch day value.", value);
            return false;
        }
        return true;
    }

    private static LocalDate toLocalDate(Long millis) {
        Instant instant = Instant.ofEpochMilli(millis);
        LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return LocalDate.of(ldt.getYear(), ldt.getMonth(), ldt.getDayOfMonth());
    }

}

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

5reactions
jeroenvscommented, Jun 9, 2020

Pardon, I’ve found the probleem and it’s indeed not a bug. Between 2.10.4 en 2.11.0 the deserializer now uses the following method:

    @Override
    protected LocalDateDeserializer withShape(JsonFormat.Shape shape) { return new LocalDateDeserializer(this, shape); }

Which creates a new deserializer, replacing my custom deserializer. I’ve solved the problem by using composition over inheritance:

public final class CustomLocalDateDeserializer extends StdDeserializer<LocalDate> {

    protected CustomLocalDateDeserializer() {
        super(LocalDate.class);
    }

    @Override
    public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
            long value = parser.getLongValue();
            if (!isValidEpochDay(value)) {
                return toLocalDate(value);
            }
        }

        return LocalDateDeserializer.INSTANCE.deserialize(parser, context);
    }

    private static boolean isValidEpochDay(long value) {
        try {
            ChronoField.EPOCH_DAY.checkValidValue(value);
        } catch (DateTimeException ex) {
            return false;
        }
        return true;
    }

    private static LocalDate toLocalDate(Long millis) {
        Instant instant = Instant.ofEpochMilli(millis);
        LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return LocalDate.of(ldt.getYear(), ldt.getMonth(), ldt.getDayOfMonth());
    }

}
0reactions
cowtowncodercommented, Jun 10, 2020

Ok, glad you figure out the issue. Sub-classing is quite fragile, unfortunately, and composition is encouraged, for this reason.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Jackson module not registered after update from Spring Boot ...
This problem occurs because JSON doesn't natively have a date format, so it represents dates as String. The String representation of a date ......
Read more >
Jackson Date - Baeldung
The @JsonDeserialize annotation is used to specify a custom deserializer to unmarshal the JSON object. Similarly, @JsonSerialize indicates a ...
Read more >
Java 8 date/time type not supported by default - You.com
Implement a custom Deserializer. Let's walk through these options. Parse the Timestamp inside the Constructor. We can declare all-args constructor inside the ...
Read more >
JavaTimeModule (Jackson datatype: JSR310 2.11.0 API)
Class that registers capability of serializing java.time objects with the Jackson core. ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new ...
Read more >
How to Handle Java 8 Dates and Time with Jackson in Spring ...
By Default, it does not handle the new Java 8 date and time classes correctly ... When we use a LocalDateTime, it will...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found