在 Spring REST Controller 中对 DateTime 使用 Jackson 反序列化

Posted

技术标签:

【中文标题】在 Spring REST Controller 中对 DateTime 使用 Jackson 反序列化【英文标题】:Use Jackson Deserialization for DateTime in Spring REST Controller 【发布时间】:2018-08-10 17:29:58 【问题描述】:

我在尝试将 POST 请求中传递给 Spring 控制器的值从 String 反序列化为 OffsetDateTime 时遇到此异常。

这是我的例外:

Failed to convert value of type 'java.lang.String' to required type 'java.time.OffsetDateTime';
    nested exception is org.springframework.core.convert.ConversionFailedException: 
        Failed to convert from type [java.lang.String] to 
           type [@org.springframework.web.bind.annotation.RequestParam java.time.OffsetDateTime]
           for value '2018-03-02T14:12:50.789+01:00';
           nested exception is java.lang.IllegalArgumentException:
                Parse attempt failed for value [2018-03-02T14:12:50.789+01:00]

我使用的是最新版的Spring-Boot - 2.0.1.BUILD-SNAPSHOT

这是我的JacksonConfig.java

package com.divinedragon.jackson.config;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import static com.fasterxml.jackson.databind.DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL;
import static com.fasterxml.jackson.databind.PropertyNamingStrategy.SNAKE_CASE;
import static com.fasterxml.jackson.databind.SerializationFeature.WRAP_ROOT_VALUE;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;

@Configuration
public class JacksonConfig 

    @Bean(name = "jacksonConverter")
    public MappingJackson2HttpMessageConverter jacksonConverter(final ObjectMapper objectMapper) 

        final MappingJackson2HttpMessageConverter httpMessageConverter = new MappingJackson2HttpMessageConverter();

        httpMessageConverter.setObjectMapper(objectMapper);

        return httpMessageConverter;
    

    @Bean
    @Primary
    public ObjectMapper objectMapper() 
        final ObjectMapper mapper = new ObjectMapper();

        mapper.enable(READ_UNKNOWN_ENUM_VALUES_AS_NULL);

        mapper.disable(FAIL_ON_UNKNOWN_PROPERTIES);
        mapper.disable(WRAP_ROOT_VALUE);

        mapper.setDateFormat(new ISO8601DateFormat());
        mapper.setPropertyNamingStrategy(SNAKE_CASE);

        mapper.registerModule(new Jdk8Module());
        mapper.registerModule(new JavaTimeModule());
        mapper.registerModule(new ParameterNamesModule());

        return mapper;
    

这是我的JacksonController.java,它是一个 Spring REST 控制器。

package com.divinedragon.jackson.controller;

import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class JacksonController 

    @GetMapping(path = "/get")
    public Map<String, OffsetDateTime> getDates() 
        return Collections.singletonMap("createdAt", OffsetDateTime.now());
    

    @PostMapping(path = "/post")
    public Map<String, OffsetDateTime> postDates(@RequestParam("created_at") final OffsetDateTime createdAt) 
        return Collections.singletonMap("createdAt", createdAt);
    

此应用程序运行,当我向 /get 端点发出请求时,我得到了使用 Jackson 正确序列化的日期值。

-> curl -s http://localhost:8080/get | python -m json.tool

    "createdAt": "2018-03-02T14:12:50.789+01:00"

当我使用 /post 端点并传递日期值时,我得到了上述异常:

-> curl -s -X POST http://localhost:8080/post --data-urlencode 'created_at=2018-03-02T14:12:50.789+01:00' | python -m json.tool

    "error": "Bad Request",
    "message": "Failed to convert value of type 'java.lang.String' to required type 'java.time.OffsetDateTime'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam java.time.OffsetDateTime] for value '2018-03-02T14:12:50.789+01:00'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2018-03-02T14:12:50.789+01:00]",
    "path": "/post",
    "status": 400,
    "timestamp": "2018-03-02T13:15:38Z"

有人可以指导我如何使用 Jackson 反序列化将值转换为 OffsetDateTime?

【问题讨论】:

【参考方案1】:

似乎没有办法通过@RequestParam 传递StringOffsetDateTime 的Jackson 拦截转换。

为了完成这项工作,我最终在post here 的帮助下编写了自己的转换器。

这是我的转换器:

@Component
public class CustomOffsetDateTimeConverter implements Converter<String, OffsetDateTime> 

    @Autowired
    private DateTimeFormatter dateTimeFormatter;

    @Override
    public OffsetDateTime convert(final String source) 
        return OffsetDateTime.parse(source, dateTimeFormatter);
    


另外,为了让 Jackson 也符合相同的 DateTimeFormat,我更新了我的 Jackson 配置。

我很快就知道,如果你想更新序列化/反序列化的格式,这是行不通的。

objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));

因此,我最终为此编写了自定义序列化器/反序列化器,然后覆盖了 JavaTimeModule 的默认值。

这是我更新的JacksonConfig.java

@Configuration
public class JacksonConfig 

    @Bean("dateTimeFormatter")
    public DateTimeFormatter dateTimeFormatter() 
        return DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING);
    

    @Bean
    @Primary
    public ObjectMapper objectMapper(final DateTimeFormatter dateTimeFormatter) 
        final ObjectMapper mapper = new ObjectMapper();

        mapper.enable(READ_UNKNOWN_ENUM_VALUES_AS_NULL);

        mapper.disable(FAIL_ON_UNKNOWN_PROPERTIES);
        mapper.disable(WRAP_ROOT_VALUE);
        mapper.disable(WRITE_DATES_AS_TIMESTAMPS);

        mapper.setPropertyNamingStrategy(SNAKE_CASE);

        final JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(OffsetDateTime.class, new CustomOffsetDateTimeSerializer(dateTimeFormatter));
        javaTimeModule.addDeserializer(OffsetDateTime.class, new CustomOffsetDateTimeDeserializer(dateTimeFormatter));

        mapper.registerModule(new Jdk8Module());
        mapper.registerModule(javaTimeModule);
        mapper.registerModule(new ParameterNamesModule());

        return mapper;
    

    @Bean(name = "jacksonConverter")
    public MappingJackson2HttpMessageConverter jacksonConverter(final ObjectMapper objectMapper) 

        final MappingJackson2HttpMessageConverter httpMessageConverter = new MappingJackson2HttpMessageConverter();

        httpMessageConverter.setObjectMapper(objectMapper);

        return httpMessageConverter;
    


class CustomOffsetDateTimeSerializer extends JsonSerializer<OffsetDateTime> 

    private final DateTimeFormatter dateTimeFormatter;

    public CustomOffsetDateTimeSerializer(final DateTimeFormatter dateTimeFormatter) 
        this.dateTimeFormatter = dateTimeFormatter;
    

    @Override
    public void serialize(final OffsetDateTime value, final JsonGenerator gen, final SerializerProvider serializers)
            throws IOException 
        gen.writeString(dateTimeFormatter.format(value));
    



@Component
class CustomOffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> 

    private final DateTimeFormatter dateTimeFormatter;

    public CustomOffsetDateTimeDeserializer(final DateTimeFormatter dateTimeFormatter) 
        this.dateTimeFormatter = dateTimeFormatter;
    

    @Override
    public OffsetDateTime deserialize(final JsonParser p, final DeserializationContext ctxt)
            throws IOException, JsonProcessingException 
        return OffsetDateTime.parse(p.getValueAsString(), dateTimeFormatter);
    

希望有一天这对某人有所帮助。

【讨论】:

以上是关于在 Spring REST Controller 中对 DateTime 使用 Jackson 反序列化的主要内容,如果未能解决你的问题,请参考以下文章

Spring REST Controller 没有响应 Angular 请求

在 Spring REST Controller 中对 DateTime 使用 Jackson 反序列化

Angular 2 + CORS + 带有 Spring Security 的 Spring Boot Rest Controller

Spring MVC - 如何在 Rest Controller 中以 JSON 形式返回简单字符串

Spring Rest Controller PUT 方法请求正文验证?

Rest Controller无法识别Spring Boot App中的GET请求