You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何为@RequestParam参数使用@JsonDeserialize

@RequestParam绑定LocalDateTime时自定义Jackson反序列化器无效的解决方法

问题场景

我尝试用Jackson自定义反序列化器处理LocalDateTime类型的@RequestParam参数,发现deserialize方法从未被调用,完全不生效。但使用@RequestBody接收封装类中的LocalDateTime参数时,该反序列化器能正常工作。

当前相关代码

@RequestParam方法(无效)

@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
 )

@RequestBody方法(正常生效)

@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
}

自定义Jackson反序列化器

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;
    }
}

当前配置类

@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());
        };
    }
}

依赖版本

<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>

问题原因

Jackson的@JsonDeserialize注解仅作用于JSON请求体的反序列化流程,也就是@RequestBody对应的处理逻辑。而@RequestParam的参数是从URL查询参数、表单参数中获取的字符串,Spring会通过自身的Converter<String, T>机制完成字符串到目标类型的转换,不会触发Jackson的反序列化器,因此直接在@RequestParam参数上标注@JsonDeserialize完全无效。

解决方案

方案1:全局注册自定义Converter

创建实现Converter<String, LocalDateTime>的类,迁移原反序列化器的逻辑,再注册到Spring转换服务中。

自定义Converter类

import org.springframework.core.convert.converter.Converter;
import org.springframework.util.ObjectUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class CustomLocalDateTimeConverter implements Converter<String, LocalDateTime> {

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

    @Override
    public LocalDateTime convert(String source) {
        String text = source.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;
    }
}

修改配置类注册Converter

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class GlobalDateConfig implements WebMvcConfigurer {

    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());
        };
    }

    // 注册自定义Converter
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new CustomLocalDateTimeConverter());
    }
}

方案2:使用@InitBinder全局绑定

通过@ControllerAdvice配合@InitBinder全局配置LocalDateTime的绑定规则。

全局绑定配置类

import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import java.beans.PropertyEditorSupport;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@ControllerAdvice
public class GlobalDateTimeBinder {

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

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                if (text == null || text.trim().isEmpty()) {
                    setValue(null);
                    return;
                }
                LocalDateTime localDateTime = null;
                try {
                    localDateTime = LocalDateTime.parse(text.trim(), FORMATTER1);
                } catch (Exception e) {
                    try {
                        LocalDate parse = LocalDate.parse(text.trim(), FORMATTER2);
                        LocalTime time = LocalTime.of(0, 0, 0);
                        localDateTime = LocalDateTime.of(parse, time);
                    } catch (Exception ignore) {
                    }
                }
                setValue(localDateTime);
            }
        });
    }
}

注意事项

两种方案二选一即可,无需同时配置。配置完成后,@RequestParam参数上的@JsonDeserialize注解可以移除。

内容的提问来源于stack exchange,提问作者wang junweiiii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 15:00:17