【问题标题】:Cannot construct instance of `java.time.LocalDate` - Spring boot, elasticseach, jackson无法构造`java.time.LocalDate`的实例-Spring boot,elasticseach,jackson
【发布时间】:2018-07-29 19:09:41
【问题描述】:

我正在使用 Spring-boot 2.0.0.M7spring-boot-starter-data-elasticsearchelasticsearch 5,但我在反序列化 LocalDate 字段时遇到错误。

我的文档是这样的:

@Document(indexName= "myIndex", type = "cluster")
public class Cluster {

    @Id
    @Field
    private Long id;
    @Field
    private String name;
    @Field
    private ClusterUrl clusterUrl;
    @Field
    private ClusterVisible clusterVisible;
}

其中 ClusterVisible 是一个包含LocalDates 的子对象:

public class ClusterVisible {

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd.MM.yyyy")
    private LocalDate start;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd.MM.yyyy")
    private LocalDate end;
}

所以我只查询一个集群 ID,我得到了这个异常:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.LocalDate` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{"id":12345,"name":"Cluster name ","clusterName":{"de":"Cluster de","it":null,"fr":null},"clusterUrl":{"de":"/url/results","it":null,"fr":null},"clusterVisible":{"start":{"year":2017,"month":"OCTOBER","dayOfMonth":9,"dayOfWeek":"MONDAY","era":"CE","dayOfYear":282,"leapYear":false,"mo"[truncated 252 chars]; line: 1, column: 388] (through reference chain: com.example.elasticsearch5.es.cluster.model.Cluster["clusterVisible"]->com.example.elasticsearch5.es.cluster.model.ClusterVisible["start"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1290)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
    at org.springframework.data.elasticsearch.core.DefaultEntityMapper.mapToObject(DefaultEntityMapper.java:65)

我已经知道我需要为java.time api 添加一些jackson 依赖项,所以我添加了:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.4</version>
</dependency>

但这暂时无济于事。我还通过使用 kibana 进行查询来检查索引中的条目。查询结果为:

...
"clusterVisible": {
    "start": {
      "year": 2017,
      "month": "OCTOBER",
      "dayOfMonth": 25,
      "dayOfWeek": "WEDNESDAY",
      "era": "CE",
      "dayOfYear": 298,
      "leapYear": false,
      "monthValue": 10,
      "chronology": {
        "id": "ISO",
        "calendarType": "iso8601"
      }
    },
    "end": {
      "year": 3000,
      "month": "JANUARY",
      "dayOfMonth": 1,
      "dayOfWeek": "WEDNESDAY",
      "era": "CE",
      "dayOfYear": 1,
      "leapYear": false,
      "monthValue": 1,
      "chronology": {
        "id": "ISO",
        "calendarType": "iso8601"
      }
    }
}

修复此错误我错过了什么?

补充:确切的错误发生在mapper.mapToObject。所以我之前创建了一个new DefaultEntityMapper(); 一些行。会不会是这个问题?

@Override
public Page<Cluster> findClustersAndScoreByText(String text) {
    QueryBuilder queryBuilder = QueryBuilders.boolQuery()
            .should(QueryBuilders.queryStringQuery(text).lenient(true).defaultOperator(Operator.OR)
                    .field("name")
                    .field("svno"));

    NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder)
            .withPageable(PageRequest.of(0, 100)).build();

    DefaultEntityMapper mapper = new DefaultEntityMapper();
    ResultsExtractor<Page<Cluster>> rs = new ResultsExtractor<Page<Cluster>>() {

        @Override
        public Page<Cluster> extract(SearchResponse response) {
            ArrayList<Cluster> hotels = new ArrayList<>();
            SearchHit[] hits = response.getHits().getHits();
            for (SearchHit hit : hits) {
                try {
                    Cluster cluster = mapper.mapToObject(hit.getSourceAsString(), Cluster.class);
                    cluster.setScore(hit.getScore());
                    hotels.add(cluster);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return new PageImpl<>(hotels, PageRequest.of(0, 100), response.getHits().getTotalHits());
        }
    };

    return elasticsearchTemplate.query(nativeSearchQuery, rs);
}

【问题讨论】:

  • 您的开始和结束数据都是对象...为什么pattern = "dd.MM.yyyy" 可以工作?
  • @cricket_007 应该可以反序列化到前端。但我删除了它,没有任何变化。
  • 该错误告诉您本地日期时间及其不可变类不存在默认构造函数,因此它不能只为json中的每个字段调用setters。为什么不为该对象编写自己的类?
  • @cricket_007 如果配置不起作用,我需要编写自己的类。我自动装配了杰克逊对象映射器,而不是使用一个新的,我得到了 localdate 的另一个例外:com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected VALUE_STRING: Expected array or string. 任何想法?
  • "start": { 是一个对象,正如错误所说。 JsonFormat.Shape.STRING 正在尝试解析一个字符串,而你没有

标签: java spring spring-boot elasticsearch jackson


【解决方案1】:

您不需要@JsonFormat。您只需要注解中的DateFormat.date 参数(kotlin):

@Field(type = Date, format = date)
val created: LocalDate = now()

Elasticsearch 不使用 Jackson,而是使用 MappingElasticsearchConverter

日期将以弹性格式保存:2020-01-02,映射为:

 "created" : {
          "type" : "date",
          "format" : "date"
        }

【讨论】:

    【解决方案2】:

    需要安装JsonFormat JsonDeserialize JsonSerialize

    private LocalDate dateOfBirth;
    
        @PastOrPresent(message = "must be past time or present")
        @Column(name = "date_of_birth", nullable = false, columnDefinition = "DATE")
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
        @JsonDeserialize(using = LocalDateDeserializer.class)
        @JsonSerialize(using = LocalDateSerializer.class)
        public LocalDate getDateOfBirth() {
            return dateOfBirth;
        }
    
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
        public void setDateOfBirth(LocalDate dateOfBirth) {
            this.dateOfBirth = dateOfBirth;
        }
    

    【讨论】:

      【解决方案3】:

      在 LocalDate 字段上添加以下注释

      @JsonDeserialize(using = LocalDateDeserializer.class)
      @JsonSerialize(using = LocalDateSerializer.class)
      private LocalDate start;
      

      【讨论】:

      • 如果这些是自动生成的存根,则此解决方案将不起作用。 @evdelacruz 的回答更有效
      【解决方案4】:

      日期/时间格式,根据 ISO 8601 是 "YYYY-MM-DD",所以您的模式应该是:

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

      代替:

      @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd.MM.yyyy")
      

      另一种方法是在您的 application.yml

      中添加
      spring:
          jackson:
              serialization:
                  WRITE_DATES_AS_TIMESTAMPS: false
      

      或直接在您的对象映射器中禁用此功能:

      objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
      

      【讨论】:

      • 如何禁用一个请求而不是所有请求的序列化?
      • @DimitriKopriwa,ObjectMapper 是一个在 ApplicationContext 中构建的 bean,可以根据需要注入。恐怕你需要使用杰克逊自定义注释来实现这一点。另一种可能性是将字段视为 DTO/VO/TO 中的字符串,然后在特定场景/请求中根据需要对其进行解析。
      猜你喜欢
      • 2020-04-21
      • 2020-11-06
      • 2020-09-27
      • 2012-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-24
      • 1970-01-01
      相关资源
      最近更新 更多