Search code examples
javaspring-bootserializationjacksondeserialization

How to use @JsonDeserialize for @RequestParam params in Spring Boot


I try to use Jackson to deserialize LocalDateTime parameters, but when I use @RequestParam, it doesn't work, the 'deserialize' method has never been called.

Here is my @RequestParam method:

@GetMapping("/search")
public String search(
    @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
    @RequestParam(value = "startTime", required = false) LocalDateTime startTime,
    @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
    @RequestParam(value = "endTime", required = false) LocalDateTime endTime
 )

Instead of @RequestParam, when I use @RequestBody accept parameters, and the 'deserialize' meothd will be called, so, what can I do for @RequestParam to use 'deserialize' method.

Here is my @RequestBody method:

@PostMapping("/test")
public LocalDateTime getBillingListByDate(@RequestBody SearchQuery searchQuery) {
    return searchQuery.getStartTime();
}

SearchQuery:

public class SearchQuery {
    @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
    private LocalDateTime startTime;
    @JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
    private LocalDateTime endTime;
// getter and setter
}

This is my Deserializer class:

public class CustomLocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> {

    private final DateTimeFormatter FORMATTER1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private final DateTimeFormatter FORMATTER2 = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public CustomLocalDateTimeDeserializer() {
        this(LocalDateTime.class);
    }


    CustomLocalDateTimeDeserializer(Class<LocalDateTime> deserializer) {
        super(deserializer);
    }

    @Override
    public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        String text = jsonParser.getText().trim();
        if (ObjectUtils.isEmpty(text)) {
            return null;
        }
        LocalDateTime localDateTime = null;
        try {
            localDateTime = LocalDateTime.parse(text, FORMATTER1);
        } catch (Exception e) {
            try {
                LocalDate parse = LocalDate.parse(text, FORMATTER2);
                LocalTime time = LocalTime.of(0, 0, 0);
                localDateTime = LocalDateTime.of(parse, time);
            } catch (Exception ignore) {
            }
        }
        return localDateTime;
    }
}

and the config:

@Configuration
public class GlobalDateConfig {

    private final String formatter = "yyyy-MM-dd HH:mm:ss";

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern(formatter);
            builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(localDateTimeFormatter));
            builder.deserializerByType(LocalDateTime.class, new CustomLocalDateTimeDeserializer());
        };
    }
}

Here is my dependency version:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.1</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.11.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.11.2</version>
</dependency>

Solution

  • Use Converters

    First, create a converter class

    @Component
    public class LocalDateTimeConverter implements Converter<String, LocalDateTime> {
            @Override
            public LocalDateTime convert(String value) {
                try {
                    return LocalDate.parse(value, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atStartOfDay();
                } catch (DateTimeParseException e1) {
                    try {
                        return LocalDateTime.parse(value, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                    } catch (DateTimeParseException ignored) {
                    }
                }
                return null;
            }
    }
    

    Then config it

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverter(new LocalDateTimeConverter());
        }
    }
    

    You can test it with these

    /search?startTime=2023-03-31 03:10:02
    /search?startTime=2023-03-31