spring-boot - 没有字符串参数构造函数/工厂方法来从字符串值 ('2018-12-14' 反序列化)

标签 spring-boot jackson spring-rest localdate

这个问题和 this 几乎一样一,但不同之处在于我试图将 String 获取到 LocalDate。这是来自 STS 的错误:

2018-12-14 00:47:04.507 WARN 6216 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value ('2018-12-14'); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value ('2018-12-14') at [Source: java.io.PushbackInputStream@73ff9989; line: 3, column: 16] (through reference chain: com.xxxxx.xxxxxx.model.request.ReservationRequest["checkin"])]

这是来自 postman 的:

{ "timestamp": 1544744824516, "status": 400, "error": "Bad Request", "exception": "org.springframework.http.converter.HttpMessageNotReadableException", "message": "JSON parse error: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value ('2018-12-14'); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value ('2018-12-14')\n at [Source: java.io.PushbackInputStream@73ff9989; line: 3, column: 16] (through reference chain: com.xxxxx.xxxxx.model.request.ReservationRequest[\"checkin\"])", "path": "/room/reservation/v1" }

POST 请求是:

{
    "id": 12345,
    "checkin": "2018-12-14",
    "checkout": "2018-12-17"
}

相关类在哪里:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

@Configuration
public class ApiConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        return new ObjectMapper();
    }

    @Bean
    public ObjectWriter objectWriter(ObjectMapper objectMapper) {
        return objectMapper.writerWithDefaultPrettyPrinter();
    }
}

import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;

public class ReservationRequest {

    private Long id;
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate checkin;
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate checkout;

    public ReservationRequest() {
        super();
    }

    public ReservationRequest(Long id, LocalDate checkin, LocalDate checkout) {
        super();
        this.id = id;
        this.checkin = checkin;
        this.checkout = checkout;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public LocalDate getCheckin() {
        return checkin;
    }

    public void setCheckin(LocalDate checkin) {
        this.checkin = checkin;
    }

    public LocalDate getCheckout() {
        return checkout;
    }

    public void setCheckout(LocalDate checkout) {
        this.checkout = checkout;
    }
}

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.xxxxx.xxxxxx.model.request.ReservationRequest;
import com.xxxxx.xxxxxx.model.response.ReservationResponse;

@RestController
@RequestMapping(ResourceConstants.ROOM_RESERVATION_V1)
public class ReservationResource {

    @RequestMapping(path = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<ReservationResponse> getAvaiableRooms(
            @RequestParam(value = "checkin") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate checkin,
            @RequestParam(value = "checkout") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate checkout) {
        return new ResponseEntity<>(new ReservationResponse(), HttpStatus.OK);
    }

    @RequestMapping(path = "", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<ReservationResponse> createReservation(@RequestBody ReservationRequest reservationRequest) {

        return new ResponseEntity<>(new ReservationResponse(), HttpStatus.CREATED);
    }

    @RequestMapping(path = "", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<ReservationResponse> updateReservation(@RequestBody ReservationRequest reservationRequest) {

        return new ResponseEntity<>(new ReservationResponse(), HttpStatus.OK);
    }

    @RequestMapping(path = "/{reservationId}", method = RequestMethod.DELETE)
    public ResponseEntity<Void> deleteReservation(@PathVariable long reservationId) {

        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

为了以防万一,我已经包含了导入内容。

无论如何,如果我将 ReservationRequest 更改为具有字符串字段而不是像这样的 LocalDate 字段,那么它不会产生错误

public class ReservationRequest {

    private Long id;
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private String checkin;
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private String checkout;

    public ReservationRequest() {
        super();
    }

    public ReservationRequest(Long id, String checkin, String checkout) {
        super();
        this.id = id;
        this.checkin = checkin;
        this.checkout = checkout;
    }

(getters and setters updated as well)
JDK 1.8; springBootVersion = '1.5.17.RELEASE';名称:'jackson-datatype-jsr310',版本:'2.9.7'

问题是为什么它不能按照 LocalDate 的预期工作?

更新:尝试过these解决方案,并添加了 @JsonSerialize 和 @JsonDeserialize,因为两者都不是 objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

工作了,现在看起来像:

public class ReservationRequest {

    private Long id;
    @JsonSerialize(using = ToStringSerializer.class)
    @JsonDeserialize(using = LocalDateDeserializer.class)
    //@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate checkin;
    @JsonSerialize(using = ToStringSerializer.class)
    @JsonDeserialize(using = LocalDateDeserializer.class)
    //@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate checkout;

    public ReservationRequest() {
        super();
    }

所以,现在看起来可行,但我不知道这是否是一个好的解决方案?

最佳答案

我认为问题出在这里

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());
    return new ObjectMapper();
}

您必须返回配置的objectMapper而不是新实例:

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
    objectMapper.registerModule(new JavaTimeModule()
            .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("M/d/yyyy")))
            .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("M/d/yyyy"))))
    return objectMapper;
}

关于spring-boot - 没有字符串参数构造函数/工厂方法来从字符串值 ('2018-12-14' 反序列化),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53771807/

相关文章:

java - gradle bootRun成功启动,但一段时间后断开连接

java - 使用 jackson 解析包含等于 java 中的 "final"关键字的变量的 JSON 的问题

java - 从请求 json 在 spring 中创建嵌套实体

java - 如何将 @Projection 分配给 @GetMapping spring servlet 端点?

java - 如何使用@XmlElement注释将REST输出映射到Spring Boot中的Dto,以便我可以获得所需格式的xml输出?

java - 用于验证来自 Google 的 oauth2 token 的 Spring Boot 应用程序

java - 如何绑定(bind)带有文件输入的 HTML 表单和后端的 Spring RestController(无 JS)

java - 在 AWS lambda 中使用 Spring data jpa 部署 Spring boot

java - 如何仅针对给定的单个端点忽略 JsonProperty 并让它适用于其他端点

spring-mvc - 用于 Restful 和正常的 Spring MVC ExceptionHandler