javamicronautmicronaut-serde

Micronaut Serialization: Deserialising LocalTime with @JsonFormat has no effect


I'm trying to deserialise the following Json String into a Java record using Micronaut 4.2.4 with Micronaut Serialization (No Jackson Implemenation involved) and having troubles to successfully parse American time values (such as sunrise, sunset, …) into a java.time.LocalTime.

{
  "results": {
    "date": "2024-02-02",
    "sunrise": "7:52:59 AM",
    "sunset": "5:27:56 PM",
    "first_light": "6:07:12 AM",
    "last_light": "7:13:44 PM",
    "dawn": "7:20:03 AM",
    "dusk": "6:00:52 PM",
    "solar_noon": "12:40:28 PM",
    "golden_hour": "4:41:39 PM",
    "day_length": "9:34:56",
    "timezone": "Europe/Zurich",
    "utc_offset": 60
  },
  "status": "OK"
}
@Serdeable
record EntityResponse(Result results, String status) {}

@Serdeable
record CollectionResponse(List<Result> results, String status) {}

@Serdeable
record Result(
    LocalDate date,
    LocalTime sunrise,
    LocalTime sunset,
    @JsonProperty("first_light") @JsonFormat(pattern = TIME_FORMAT, locale = "en_US")
        LocalTime firstLight,
    @JsonProperty("last_light") @JsonFormat(pattern = TIME_FORMAT, locale = "en_US")
        LocalTime lastLight,
    LocalTime dawn,
    LocalTime dusk,
    @JsonProperty("solar_noon") @JsonFormat(pattern = TIME_FORMAT, locale = "en_US")
        LocalTime solarNoon,
    @JsonProperty("golden_hour") @JsonFormat(pattern = TIME_FORMAT, locale = "en_US")
        LocalTime goldenHour,
    Duration dayLength,
    ZoneId timezone,
    ZoneOffset utcOffset) {
  public static final String TIME_FORMAT = "h:mm:ss a";
}

Notice that I'm using @JsonFormat(pattern = TIME_FORMAT, locale = "en_US") to indicate with what pattern and locale the DateTimeFormatter should parse the string value.

But when I am trying to execute this test

MicronautTest
public class MyTest {

    @Inject
    private ObjectMapper objectMapper;

    @Test
    @DisplayName("Verify that the deserialization works")
    public void deserialize() throws IOException {

        // given
        final String json =
                """
                        {
                          "results": {
                            "date": "2024-02-02",
                            "sunrise": "7:52:59 AM",
                            "sunset": "5:27:56 PM",
                            "first_light": "6:07:12 AM",
                            "last_light": "7:13:44 PM",
                            "dawn": "7:20:03 AM",
                            "dusk": "6:00:52 PM",
                            "solar_noon": "12:40:28 PM",
                            "golden_hour": "4:41:39 PM",
                            "day_length": "9:34:56",
                            "timezone": "Europe/Zurich",
                            "utc_offset": 60
                          },
                          "status": "OK"
                        }
                        """;

        // when
        SunriseSunsetClient.EntityResponse response =
                objectMapper.readValue(json, SunriseSunsetClient.EntityResponse.class);

        // then
        assertEquals("OK", response.status());
        assertNotNull(response.results());
    }
}

the test fails with this exception

Caused by: java.time.format.DateTimeParseException: Text '7:52:59 AM' could not be parsed at index 0
    at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2108)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:2010)
    at io.micronaut.serde.support.serdes.DefaultFormattedTemporalSerde.deserialize(DefaultFormattedTemporalSerde.java:71)
    at io.micronaut.serde.support.serdes.DefaultFormattedTemporalSerde.deserialize(DefaultFormattedTemporalSerde.java:35)
    at io.micronaut.serde.Deserializer.deserializeNullable(Deserializer.java:85)
    at io.micronaut.serde.support.deserializers.DeserBean$DerProperty.deserializeAndSetConstructorValue(DeserBean.java:838)
    ... 34 more
    Suppressed: java.lang.NumberFormatException: Character : is neither a decimal digit number, decimal point, nor "e" notation exponential mark.
        at java.base/java.math.BigDecimal.<init>(BigDecimal.java:608)
        at java.base/java.math.BigDecimal.<init>(BigDecimal.java:497)
        at java.base/java.math.BigDecimal.<init>(BigDecimal.java:903)
        at io.micronaut.serde.support.serdes.NumericSupportTemporalSerde.deserializeFallback(NumericSupportTemporalSerde.java:62)
        at io.micronaut.serde.support.serdes.DefaultFormattedTemporalSerde.deserialize(DefaultFormattedTemporalSerde.java:73)

Isn't JsonFormat not fully supported by Micronaut Serialization or am I using it wrong?


Solution

  • This is rather a stupid one. I did not annotate all the LocalTime fields and therefore the error is explainable. Please find complete annotated records below.

    @Serdeable
    record Result(
        LocalDate date,
        @JsonFormat(pattern = TIME_FORMAT, locale = "en_US") LocalTime sunrise,
        @JsonFormat(pattern = TIME_FORMAT, locale = "en_US") LocalTime sunset,
        @JsonProperty("first_light") @JsonFormat(pattern = TIME_FORMAT, locale = "en_US")
            LocalTime firstLight,
        @JsonProperty("last_light") @JsonFormat(pattern = TIME_FORMAT, locale = "en_US")
            LocalTime lastLight,
        @JsonFormat(pattern = TIME_FORMAT, locale = "en_US") LocalTime dawn,
        @JsonFormat(pattern = TIME_FORMAT, locale = "en_US")LocalTime dusk,
        @JsonProperty("solar_noon") @JsonFormat(pattern = TIME_FORMAT, locale = "en_US")
            LocalTime solarNoon,
        @JsonProperty("golden_hour") @JsonFormat(pattern = TIME_FORMAT, locale = "en_US")
            LocalTime goldenHour,
        Duration dayLength,
        ZoneId timezone,
        ZoneOffset utcOffset) {
      public static final String TIME_FORMAT = "h:mm:ss a";
    }