【问题标题】:ObjectMapper can't deserialize without default constructor after upgrade to Spring Boot 2升级到 Spring Boot 2 后,ObjectMapper 无法在没有默认构造函数的情况下反序列化
【发布时间】:2018-06-28 01:48:54
【问题描述】:

我有以下 DTO:

@Value
public class PracticeResults {
    @NotNull
    Map<Long, Boolean> wordAnswers;
}

@Value
public class ProfileMetaDto {

    @NotEmpty
    String name;
    @Email
    String email;
    @Size(min = 5)
    String password;
}

@Value 是一个生成构造函数的 Lombok 注释。这意味着这个类没有无参数构造函数。

我使用了 Spring Boot 1.4.3.RELEASE,ObjectMapper bean 能够从 JSON 中反序列化此类对象。

升级到 Spring Boot 2.0.0.M7 后,我收到以下异常:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of PracticeResults (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

Spring Boot 1.4.3 中使用的 Jackson 版本是 2.8.10,而 Spring Boot 2.0.0.M7 中使用的是 2.9.2

我试过用谷歌搜索这个问题,但只找到@JsonCreator@JsonProperty 的解决方案。

那么,为什么它适用于 Spring Boot 1.4.3 而适用于 Spring Boot 2?是否可以将 bean 配置为与旧版本相同的行为方式?

【问题讨论】:

  • 只是想知道,您是否尝试将 Jackson 更新到最新的稳定版本 2.9.4?更改日志包含一些与地图反序列化相关的特定修复。 github.com/FasterXML/jackson/wiki/Jackson-Release-2.9.3
  • @LuisAguilar 我尝试了较新的版本,但没有任何改变。我有不同的 DTO,其中有一些字符串也无法反序列化。我认为这与 Spring 的对象映射器配置有关,但我找不到与默认构造函数相关的任何内容。
  • 在每种情况下您使用的是哪个版本的 Lombok?
  • @AndyWilkinson 哇!我已经为 spring boot 1.4.3 尝试了新的 lombok 版本并且得到了同样的例外。是不是说lombok 1.16.12生成了带有@Value注解的无参数构造函数?
  • 我尝试用谷歌搜索这个问题,但只找到@JsonCreator@JsonProperty 的解决方案,这就是答案。 Jackson 要求构造函数上的这些注释知道如何将值映射到其中,或者它需要 default no-args constructor,正如错误消息明确指出的那样,而您两者都没有,这就是它不起作用的原因。

标签: java spring spring-boot jackson lombok


【解决方案1】:

由于 Lombok 版本 1.16.20 中的重大更改,您需要在您的 lombok.config 文件中设置以下属性(如果您没有此文件,您可以在项目根目录中创建它):

lombok.anyConstructor.addConstructorProperties=true

Lombok 更新日志中对此进行了描述:https://projectlombok.org/changelog

之后,Jackson 应该再次接受 @Value。

您可能有兴趣在此处关注相关的 GitHub 问题,尽管它是关于 @Data:https://github.com/rzwitserloot/lombok/issues/1563

【讨论】:

  • 非常好! :) 我只会在此处添加来自 lombok 更新日志的完整描述:BREAKING CHANGE: lombok config key lombok.anyConstructor.suppressConstructorProperties is now deprecated and defaults to true, that is, by default lombok no longer automatically generates @ConstructorProperties annotations. New config key lombok.anyConstructor.addConstructorProperties now exists; set it to true if you want the old behavior. Oracle more or less broke this annotation with the release of JDK9, necessitating this breaking change.
【解决方案2】:

我已将 lombok 版本升级到:'org.projectlombok:lombok:1.18.0',它对我有用。

【讨论】:

  • 嗯,我在幻形灵身上没有看到任何相关的东西
【解决方案3】:

解决此问题的另一种方法。使用 Jackson parameter names module,默认包含在 spring boot 2 中。在此之后杰克逊可以反序列化对象。但仅当您在对象中有超过 1 个属性时它才有效。如果是单一属性,我会收到以下错误消息:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `SomeClassName` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

原因如下:

可用于将构造函数和工厂方法定义为用于实例化关联类的新实例的标记注释。

注意:注释创建者方法(构造函数、工厂方法)时,方法必须是:

  • 没有JsonProperty参数注释的单参数构造函数/工厂方法:如果是这样,这就是所谓的“委托创建者”,在这种情况下,杰克逊首先将JSON绑定到参数类型,然后调用创建者。这通常与JsonValue(用于序列化)结合使用。
  • 构造函数/工厂方法,其中每个参数都使用JsonPropertyJacksonInject进行注释,以指示要绑定到的属性名称

还请注意,所有JsonProperty 注释必须指定实际名称(“默认”不是空字符串),除非您使用可以检测参数名称的扩展模块之一;这是因为 8 之前的默认 JDK 版本无法从字节码存储和/或检索参数名称。但是对于 JDK 8(或使用辅助库,如 Paranamer,或其他 JVM 语言,如 Scala 或 Kotlin),指定名称是可选的。

为了使用 Lombok 处理这种情况,我使用了以下解决方法:

@Value
@AllArgsConstructor(onConstructor = @__(@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)))
class SomeClassName {...}

【讨论】:

  • 不工作,抛出:java.lang.IllegalArgumentException:类型 com.x.dto.y.PersonDTO 的类型定义无效:参数 #0 没有属性名称,不可注入:不能用作 Creator [com. x.dto.y.PersonDTO,注释:{interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=PROPERTIES)}] [来源:未知;行:-1,列:-1] 在 com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3750) ~[jackson-databind-2.9.8.jar:2.9.8]
【解决方案4】:

我遇到了这个问题,并且对我有用的解决方案是创建一个没有字段的 默认构造函数,然后问题就消失了。

【讨论】:

  • 如果我想创建非可变对象就会成为一个问题
  • @kapellan 你是对的。看看我的回答,你会发现它很有用。
【解决方案5】:

如果您的类中有一些最终字段,并且您希望对象创建者使用特定的构造函数和 @Data 注释一起反序列化您的 POJO,我建议使用@JsonCreator(mode = JsonCreator.Mode.PROPERTIES) 注释构造函数,并使用@JsonProperty("&lt;field_name&gt;") 注释具体所需的构造函数参数。示例如下:

package test.model;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.util.Date;

@Data
public class KafkaEventPojo {
    private final String executionId;
    private final String folder;
    private final Date created_at;

    private Date updated_at;
    // ... other params
    
    /**
     * Instantiates a new Kafka event pojo.
     *
     * @param executionId the execution or flow id of the event lifecycle
     * @param folder      the folder to be retrieved
     */
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public KafkaEventPojo(@JsonProperty("executionId") String executionId, @JsonProperty("folder") String folder) {
        this(executionId, folder, new Date(System.currentTimeMillis()));
    }

    private KafkaEventPojo(String executionId, String folder, Date date) {
        this.executionId = executionId;
        this.folder = folder;

        this.created_at = date;
        this.updated_at = date;
    }
    
    // ... other logic
}

【讨论】:

    【解决方案6】:

    正如其他人所说,您需要添加更多 @Value 注释以告诉 Lombok 在生成的构造函数上写入 @JsonCreator

    @AllArgsConstructor(onConstructor_={@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)})
    

    注意如果您使用的是 JDK8+,这里的语法与其他答案略有不同(这在 javadoc 中有所提及)

    如果您使用的是 freefair gradle 插件,您还需要对其进行配置以在 build.gradle 中添加构造函数属性:

    lombok {
        config['lombok.anyConstructor.addConstructorProperties'] = 'true'
    }
    

    【讨论】:

      【解决方案7】:

      我遇到了这个问题,但我的解决方案不涉及添加:(mode = JsonCreator.Mode.PROPERTIES)

      真正解决我问题的是构造函数中的@JsonProperty

      我的最终结果是这样的:

      @Value
      public class ServerTimeResponse {
      
          long serverTime;
      
          @JsonCreator
          public ServerTimeResponse(@JsonProperty("serverTime") long serverTime) {
              this.serverTime = serverTime;
          }
      
      }
      

      【讨论】:

      【解决方案8】:

      如果您在本机模式下运行并且由于反射解决了使用 @RegisterForReflection 注释您的类并重新构建应用程序并运行您的错误将得到解决

      link上搜索信息

      【讨论】:

        猜你喜欢
        • 2021-03-25
        • 2015-08-14
        • 1970-01-01
        • 2018-08-18
        • 1970-01-01
        • 2014-05-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多