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

115

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

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

但我无法弄清楚如何让Spring框架将birthData处理为"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;
    }
}

用户存储库类:

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

    }
}

5
请将https://github.com/FasterXML/jackson-modules-java8添加为pom.xml文件的依赖项。 - Cepr0
谢谢,@Cepr0!我在 pom 文件中添加了依赖项,现在它可以完美运行了! - zavanton
这个回答解决了你的问题吗?Spring Data JPA - ZonedDateTime格式的JSON序列化 - undefined
7个回答

143

为了进行序列化和反序列化,您需要使用jackson依赖。

请添加以下依赖:

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>

接下来,您需要告诉Jackson ObjectMapper使用JavaTimeModule模块。 为此, 在主类中自动装配ObjectMapper并向其注册JavaTimeModule。

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

此后,您的LocalDate和LocalDateTime应该能够正确地进行序列化和反序列化。


3
我刚刚不得不添加Gradle依赖项来修复它。 - Tatha
8
这部分让它对我有效:objectMapper.registerModule(new JavaTimeModule()); - 0cnLaroche
3
看起来没有自动注册,我必须显式地注册才能工作。 - Yoga Gowda
1
你也可以让模块自动发现,而不是显式地注册它们。例如:new ObjectMapper().findAndRegisterModules(); 但是,如果你混合使用手动和自动注册,只有一个注册会生效。来源:FasterXML jackson-modules-java8 - DaddyMoe
我刚刚注册了JavaTimeModule,而没有添加依赖项,它可以工作!不过需要一些Jackson的依赖。 - Katia Savina
显示剩余3条评论

36

Spring Boot 2.2.2 / Gradle:

Gradle(build.gradle):

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

实体 (User.class):

LocalDate dateOfBirth;

代码:

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

谢谢,我尝试了很多解决方案,但都没起作用,但这个是最好的。我怎样才能学习并获取更多有关这个类功能的细节? - Dhwanil Patel
1
yyyy-mm-dd 格式已在 ISO 8601 下标准化。JSR-310 指定了对 Java 8 日期和时间 API 的补充,以纳入 ISO8601。上面显示的依赖项是一个补丁,用于向 Jackson 版本 2.85 之前添加此功能。版本 2.85 及更高版本支持 Java 日期和时间 API。更多信息请参见:https://github.com/FasterXML/jackson-modules-java8/tree/master/datetime - sparkyspider
除了这个正确的答案之外,还要注意,你可以让模块自动发现而不是手动注册它们。例如: new ObjectMapper().findAndRegisterModules(); 话虽如此,如果你混合使用手动和自动注册,只有一个注册会生效。来源:FasterXML jackson-modules-java8 - DaddyMoe

27

我遇到了类似的问题,通过做两个更改解决了这个问题

  1. application.yaml文件中添加以下条目
Translated text:

我曾经遇到过一个类似的问题,我通过进行两项更改来解决。

  1. application.yaml文件中添加以下内容
spring:
    jackson:
        serialization.write_dates_as_timestamps: false
  1. 给POJO的LocalDate字段添加以下两个注释
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)

例子

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

示例请求格式:

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

示例请求格式为数组

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

2
非常感谢!你节省了我的时间!我还在pojo中添加了这行代码@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")。 - Orkhan Hasanli
这个看起来对我很干净。 - CN1002
就我所知,修改application.yaml并不是必须的,但需要添加依赖项jackson-datatype-jsr310,并按照建议包含@JsonFormat - xlm

16

结果表明,我们不能忘记将jacson依赖项包含在pom文件中。这对我解决了问题:

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

7
如果即使添加了这些依赖项仍然无法工作,我该怎么办?有没有办法让Json忽略这个参数或类似的东西? - coretechie

8

我刚刚为此奋斗了3个小时。我要感谢Dherik的回答(关于AMQP的奖励材料),因为它让我离我的答案更近了一步,但只供参考。

我在SpringBootApplication中的对象映射器中注册了JavaTimeModule,就像这样:

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

然而,我通过STOMP连接接收的即时消息仍然无法反序列化。然后我意识到我无意中创建了一个MappingJackson2MessageConverter,它创建了第二个ObjectMapper。所以我想这个故事的寓意是:你确定你调整了所有的ObjectMappers吗?在我的情况下,我用已注册JavaTimeModule的外部版本替换了MappingJackson2MessageConverter.objectMapper,一切都很好:
@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;
}

8

我在每个项目中所做的事情是以上选项的混合。

首先,添加jsr310依赖项:

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

重要细节:将此依赖项放在您的依赖项列表的顶部。我已经看到一个项目,在 pom.xml 中加入了这个依赖项,但 Localdate 错误仍然存在。但是更改依赖项的顺序后,错误消失了。

在您的 /src/main/resources/application.yml 文件中,设置 write-dates-as-timestamps 属性:

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

创建一个ObjectMapper bean,如下所示:

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

}

按照这个配置,转换总是在Spring Boot 1.5.x上正常工作,没有任何错误。

额外奖励: Spring AMQP队列配置

使用Spring AMQP时,请注意如果您有一个新的Jackson2JsonMessageConverter实例(创建SimpleRabbitListenerContainerFactory时很常见),需要将ObjectMapper bean传递给它,例如:

Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(objectMapper);

否则,您将收到相同的错误。

0
你需要将以下代码添加到你的pom.xml文件中 modules-java8
<!--    Java 8 Date/time    -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接