Search code examples
json-deserializationmicronautjackson-databind

Micronaut @JsonDeserialize not working as expected


OS: Windows 11 JDK: corretto-17.0.6 IDE: IntelliJ IDEA 2022.3.1 Micronaut Application Version: 3.7.3

I have a Micronaut application to do some basic IO operations. I have a DTO class that has a duration field, but I want this field to work in milliseconds rather than seconds. Here's my POST request:

    @Post
    public EventBean create(@Body EventBean eventBean) {
        return eventBean;
    }

and JSON body sent:

{
    "from": "2023-03-05T12:26:43.538Z",
    "duration": 600000
}

expected the same but recieved

{
    "from": "2023-03-05T12:26:43.538Z",
    "duration": 600000000
}

and EventBean class:

@Introspected
public class EventBean {
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC")
    private final Instant from;

    @JsonSerialize(converter = DurationSerializer.class)
    @JsonDeserialize(converter = DurationDeserializer.class)
    private final Duration duration;

    public EventBean(Instant from, Duration duration) {
        this.from = from;
        this.duration = duration;
    }

    public Instant getFrom() {
        return from;
    }

    public Duration getDuration() {
        return duration;
    }

    public static class DurationSerializer extends StdConverter<Duration, Long> {
        @Override public Long convert(Duration value) {
            return value.toMillis();
        }
    }

    public static class DurationDeserializer extends StdConverter<Long, Duration> {
        @Override public Duration convert(Long value) {
            return Duration.ofMillis(value);
        }
    }
}

But DurationDeserializer class is not being used while deserializing my JSON from body. ( DurationSerializer is working just fine )

I also tried using JsonDeserializer<T> class and using field in JsonDeserialize annotation but to no avail.

EDIT I found a workaround, I override the default DurationDeserializer by jackson using micronaut @Replaces as follows:

@Replaces(value = com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer.class)
public class DurationDeserializer extends JsonDeserializer<Duration> {
    @Override public Duration deserialize(JsonParser p, DeserializationContext ctxt)
        throws IOException {
        return Duration.ofMillis(p.getLongValue());
    }
}

Solution

  • I recommend to configure date/time serialisation globally in your application.yml

    jackson:
      dateFormat: yyyyMMdd
      timeZone: UTC
      serialization:
        writeDatesAsTimestamps: false
    

    This will do the Instant deser for you without using any additional Jackson annotation on your POJO.

    In order to make the duration deser work you need to make sure that your serializer/deserializer are Micronaut beans and I recommend to have them in separate Java source files.

    @Singleton
    public class DurationSerializer extends StdConverter<Duration, Long> {
            @Override public Long convert(Duration value) {
                return value.toMillis();
            }
        }
    
    @Singleton
    public class DurationDeserializer extends StdConverter<Long, Duration> {
            @Override public Duration convert(Long value) {
                return Duration.ofMillis(value);
            }
    }