JSON 解析错误: 无法构造 java.time 的实例。 LocalDate: 没有字符串参数构造函数/工厂方法从字符串值反序列化

我是 Spring Data REST 项目的新手,我正在尝试创建我的第一个 RESTful 服务。任务很简单,但我卡住了。

我想使用 RESTful API 对存储在嵌入式数据库中的用户数据执行 CRUD 操作。

但是我不知道如何使 Spring 框架处理“1999-12-15”这样的生日数据并将其存储为 LocalDate。@ JsonFormat 注释没有帮助。

目前我得到了一个错误:

HTTP/1.1 400
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 24 Aug 2017 13:36:51 GMT
Connection: close


{"cause":{"cause":null,"message":"Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value ('1999-10-10')\n
at [Source: org.apache.catalina.connector.CoyoteInputStream@4ee2a60e;
line: 1, column: 65] (through reference chain: ru.zavanton.entities.User[\"birthDate\"])"},
"message":"JSON parse error: Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value ('1999-10-10'); 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 ('1999-10-10')\n
at [Source: org.apache.catalina.connector.CoyoteInputStream@4ee2a60e; line: 1, column: 65] (through reference chain: ru.zavanton.entities.User[\"birthDate\"])"}

如何让它工作,以便客户打电话这样:

curl -i -X POST -H "Content-Type:application/json" -d "{  \"firstName\" : \"John\",  \"lastName\" : \"Johnson\", \"birthDate\" : \"1999-10-10\", \"email\" : \"john@example.com\" }" http://localhost:8080/users

将实际将实体存储到数据库中。

下面是关于这些类的信息。

用户类:

package ru.zavanton.entities;




import com.fasterxml.jackson.annotation.JsonFormat;


import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDate;


@Entity
public class User {


@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;


private String firstName;
private String lastName;


@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;


private String email;
private String password;


public long getId() {
return id;
}


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


public String getFirstName() {
return firstName;
}


public void setFirstName(String firstName) {
this.firstName = firstName;
}


public String getLastName() {
return lastName;
}


public void setLastName(String lastName) {
this.lastName = lastName;
}


public LocalDate getBirthDate() {
return birthDate;
}


public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}


public String getEmail() {
return email;
}


public void setEmail(String email) {
this.email = email;
}


public String getPassword() {
return password;
}


public void setPassword(String password) {
this.password = password;
}
}

UserRepository 类:

package ru.zavanton.repositories;


import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import ru.zavanton.entities.User;


@RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends PagingAndSortingRepository<User, Long> {


User findByEmail(@Param("email") String email);


}

申请类别:

package ru.zavanton;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class Application {


public static void main(String[] args) {


SpringApplication.run(Application.class, args);


}
}
166459 次浏览

As it turns out, one should not forget to include jacson dependency into the pom file. This solved the issue for me:

<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

You need jackson dependency for this serialization and deserialization.

Add this dependency:

Gradle:

compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4")

Maven:

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

After that, You need to tell Jackson ObjectMapper to use JavaTimeModule. To do that, Autowire ObjectMapper in the main class and register JavaTimeModule to it.

import javax.annotation.PostConstruct;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;


@SpringBootApplication
public class MockEmployeeApplication {


@Autowired
private ObjectMapper objectMapper;


public static void main(String[] args) {
SpringApplication.run(MockEmployeeApplication.class, args);


}


@PostConstruct
public void setUp() {
objectMapper.registerModule(new JavaTimeModule());
}
}

After that, Your LocalDate and LocalDateTime should be serialized and deserialized correctly.

Well, what I do on every project is a mix of the options above.

First, add the jsr310 dependency:

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

Important detail: put this dependency on the top of your depedencies list. I already see a project where the Localdate error persists even with this dependency on the pom.xml. But changing the order of the depedency the error was gone.

On your /src/main/resources/application.yml file, setup the write-dates-as-timestamps property:

spring:
jackson:
serialization:
write-dates-as-timestamps: false

And create a ObjectMapper bean as this:

@Configuration
public class WebConfigurer {


@Bean
@Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper;
}


}

Following this configuration, the conversion always work on Spring Boot 1.5.x without any error.

Bonus: Spring AMQP Queue configuration

Working with Spring AMQP, pay attention if you have a new instance of Jackson2JsonMessageConverter (common thing when creating a SimpleRabbitListenerContainerFactory). You need to pass the ObjectMapper bean to it, like:

Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(objectMapper);

Otherwise, you will receive the same error.

I had a similar issue which I solved by making two changes

  1. Added below entry in application.yaml file
spring:
jackson:
serialization.write_dates_as_timestamps: false
  1. Add below two annotations to the POJO's LocalDate field
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)

Example

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;


public class Customer   {
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
protected LocalDate birthdate;
}

Eample request format:

{"birthdate": "2019-11-28"}

Example request format as array

{"birthdate":[2019,11,18]}

Spring Boot 2.2.2 / Gradle:

Gradle (build.gradle):

implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")

Entity (User.class):

LocalDate dateOfBirth;

Code:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
User user = mapper.readValue(json, User.class);

I have just wrestled with this for 3 hours. I credit the answer from Dherik (Bonus material about AMQP) for bringing me within striking distance of MY answer, YMMV.

I registered the JavaTimeModule in my object mapper in my SpringBootApplication like this:

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

However my Instants that were coming over the STOMP connection were still not deserialising. Then I realised I had inadvertantly created a MappingJackson2MessageConverter which creates a second ObjectMapper. So I guess the moral of the story is: Are you sure you have adjusted all your ObjectMappers? In my case I replaced the MappingJackson2MessageConverter.objectMapper with the outer version that has the JavaTimeModule registered, and all is well:

@Autowired
ObjectMapper objectMapper;


@Bean
public WebSocketStompClient webSocketStompClient(WebSocketClient webSocketClient,
StompSessionHandler stompSessionHandler) {
WebSocketStompClient webSocketStompClient = new WebSocketStompClient(webSocketClient);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setObjectMapper(objectMapper);
webSocketStompClient.setMessageConverter(converter);
webSocketStompClient.connect("http://localhost:8080/myapp", stompSessionHandler);
return webSocketStompClient;
}