Ошибка синтаксического анализа JSON: невозможно создать экземпляр java.time.LocalDate: нет конструктора конструкторов /factory для десериализации из значения String

Я новичок в проекте Spring Data REST, и я пытаюсь создать свой первый RESTful-сервис. Задача проста, но я застрял.

Я хочу выполнять операции CRUD с пользовательскими данными, хранящимися во встроенной базе данных, с использованием RESTful API.

Но я не могу понять, как сделать фреймворк 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: [email protected]; 
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: [email protected]; 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\" : \"[email protected]\" }" 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);

    }
}

Ответ 1

Для этой сериализации и десериализации вам нужна зависимость Джексона.

Добавьте эту зависимость:

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. Чтобы сделать это, Autowire 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 должны быть сериализованы и десериализованы правильно.

Ответ 2

Как оказалось, не следует забывать включать зависимость 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>

Ответ 3

Если это вызвало проблему из-за API Instant date, добавьте следующие атрибуты Джексона.

spring:
    application:
        name: data
    jackson:
serialization.write_dates_as_timestamps: false 

^ - добавьте это в свой файл yml

jpa:
    open-in-view: false

Ответ 4

Ну, то, что я делаю в каждом проекте, - это сочетание вариантов выше.

Сначала добавьте зависимость jsr310:

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

Важная деталь: поместите эту зависимость в верхнюю часть вашего списка зависимостей. Я уже вижу проект, в котором ошибка Localdate сохраняется даже с этой зависимостью от pom.xml. Но при изменении порядка зависимости ошибка исчезла.

В вашем файле /src/main/resources/application.yml установите свойство write-dates-as-timestamps:

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

И создайте бин ObjectMapper следующим образом:

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

Работая с Spring AMQP, обратите внимание, если у вас есть новый экземпляр Jackson2JsonMessageConverter (обычное дело при создании SimpleRabbitListenerContainerFactory). Вам нужно передать ему bean-компонент ObjectMapper, например:

Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(objectMapper);

В противном случае вы получите ту же ошибку.