Lombok 1.18.0和Jackson 2.9.6不能一起使用

44
更新后反序列化出现故障。

我将我的微服务从 Spring 1.5.10.RELEASE 更新到了 Spring 2.0.3.RELEASE,同时也把 lombok1.16.14 升级到了 1.18.0jackson-datatype-jsr310 也从 2.9.4 升级到了 2.9.6

JSON 字符串 -

{"heading":"Validation failed","detail":"field must not be null"}

这个类 -

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {

   private final String heading;
   private final String detail;
   private String type;
}

方法调用 -

ErrorDetail errorDetail = asObject(jsonString, ErrorDetail.class);

反序列化所使用的方法 -
import com.fasterxml.jackson.databind.ObjectMapper;
// more imports and class defination.

private static <T> T asObject(final String str, Class<T> clazz) {
    try {
        return new ObjectMapper().readValue(str, clazz);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

错误 -
java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.foo.bar.ErrorDetail` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"heading":"Validation failed","detail":"field must not be null"}"; line: 1, column: 2]

你尝试给ErrorDetail类添加构造函数了吗? - Tugrul
5个回答

85
Lombok在1.16.20版本中停止为构造函数生成@ConstructorProperties(请参见changelog),因为它可能会破坏使用模块的Java 9+应用程序。该注释包含构造函数参数的名称(编译类时将其删除,因此这是一个解决方法,以便仍然可以在运行时检索参数名称)。由于默认情况下现在不再生成注释,因此Jackson无法将字段名称映射到构造函数参数。 解决方案1: 使用@NoArgsConstructor@Setter,但您将失去不可变性(如果这对您很重要)。 更新:只使用@NoArgsConstructor@Getter(不使用@Setter)也可以(因为INFER_PROPERTY_MUTATORS=true)。通过这种方式,您可以至少从常规(非反射)代码中保持类的不可变性。 解决方案2: 配置lombok重新生成注释,使用包含以下行的文件lombok.anyConstructor.addConstructorProperties = true。(如果您正在使用模块,请确保java.desktop在您的模块路径上。)添加文件后,请清理并重新编译。

解决方案3: 按照@Randakar的回答所述,结合Jackson的构建器支持和lombok的(@Jacksonized)@Builder/@SuperBuilder使用。

解决方案4: 在使用javac编译(Java 8及以上版本),在命令中添加-parameters。这将在生成的类文件中存储构造函数和方法的参数名称,以便可以通过反射检索它们。


3
谢谢。第一个选项解决了问题。然而,第二个选项没有起作用。 - JHS
4
  1. 你是否将 lombok.config 文件添加到项目的根目录中?还要注意,在这样做之后,您需要清理并重新编译。
- Jan Rieke
@JanRieke - 你能帮我解决这个相关问题吗 - https://stackoverflow.com/questions/56245674/ide-does-not-show-getters-and-setters-generated-by-lombok-for-a-jackson-annotate - MasterJoe
1
NoArgsConstructor 不仅会失去不可变性,而且无法检查 null。 - addlistener
在我的情况下,Solution 1 不够用,我还必须添加 @AllArgsConstructor 才能消除另一个错误 _constructor ... in class ... cannot be applied to given types_。使用 Java 1.8.0_265、Lombok 1.18.4 和 Jackson 2.9.5。 - Marco Lackovic
在使用@Jacksonized时,不要忘记添加@Builder - Bludwarf

18

编辑:此答案现在已经有些过时:有一个新的@Jacksonized注释,来自https://projectlombok.org/features/experimental/Jacksonized,它处理了本答案中大部分样板文件。


使jackson和lombok良好地协作的最佳方法是始终使您的DTO不可变,并告诉jackson使用构建器将其反序列化为对象。

不可变对象是个好主意,因为当字段不能在原地被修改时,编译器可以进行更积极的优化。

为此,您需要两个注释:JsonDeserialize和JsonPojoBuilder。

示例:

@Builder
@Value // instead of @Data
@RequiredArgsConstructor
@NonNull // Best practice, see below.
@JsonDeserialize(builder = ErrorDetail.ErrorDetailBuilder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {

   private final String heading;

   // Set defaults if fields can be missing, like this:
   @Builder.Default
   private final String detail = "default detail";

   // Example of how to do optional fields, you will need to configure
   // your object mapper to support that and include the JDK 8 module in your dependencies..
   @Builder.Default
   private Optional<String> type = Optional.empty()

   @JsonPOJOBuilder(withPrefix = "")
   public static final class ErrorDetailBuilder {
   }
}

32
哇,这是一大堆针对一个旨在消除样板文件的库的注释。 - Christophe Bouhier
1
@ChristopheBouhier 大多数类级别的注释可以在创建Jackson ObjectMapper时进行一次配置,我想它们在这里包含只是为了演示目的;-) - gilgwath
5
实际上,所有这些内容都已经合并成为一个注解:@Jacksonized。https://projectlombok.org/features/experimental/Jacksonized - Randakar
在使用@Jacksonized时,不要忘记添加@Builder - Bludwarf

2
您想反序列化一个包含final字段的类。所以您需要声明一个包含final字段的构造函数来进行反序列化。
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDetail {

private final String heading;
private final String detail;
private String type;

@JsonCreator
public ErrorDetail(@JsonProperty("heading") String heading, @JsonProperty("detail") String detail) {
    this.heading = heading;
    this.detail = detail;
}
}

当使用mapper反序列化时,需要将属性 MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS 设置为 false
private static <T> T asObject(final String str, Class<T> clazz) {
    try {
        return new ObjectMapper().configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS,false).readValue(str, clazz);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

如上述解决方案所提到的,您可以使用@Jacksonized(与@Builder一起)。 - Bludwarf

0

解决方案4

  • 自己编写NoArgsConstructor。这对我来说至少在使用lombok 1.18.8和Jackson 2.9.9时有效。
    @Builder
    @Getter
    @AllArgsConstructor
    public class EventDTO {

        private String id;
        private Integer isCancelled;

        private String recurringEventId;

        private String summary;
        private String description;
        private String location;
        private String startDateTime;
        private String endDateTime;

        /**
         * Make Jackson happy
         */
        public EventDTO() {
        }
    }

-5

在我看来,使用@Data注释是一种不好的方法。请将@Data更改为@Getter@Setter@EqualsAndHashCode等。

如果这样做有帮助,请在此处写下来。

更新

我建议@Data创建@RequiredArgsConstructor,它是具有final字段且没有private String type的构造函数。


为什么@Data不好? - monamona
为什么@Data不好? - undefined

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