Lombok 1.18.0 和 Jackson 2.9.6 不能一起工作

Lombok 1.18.0 and Jackson 2.9.6 not working together

更新后反序列化失败。

我将微服务从 Spring 1.5.10.RELEASE 更新为 Spring 2.0.3.RELEASE,并将 lombok1.16.14 更新为 1.18.0jackson-datatype-jsr3102.9.42.9.6.

JSON字符串-

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

Class-

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

在我看来,使用 @Data 注释是一种糟糕的方法。 请将 @Data 更改为 @Getting , @Setter, @EqualsAndHashcode 等等..

请写在这里,如果有帮助的话。

更新

我建议 @Data 创建 @RequiredArgsConstructor,它是具有最终字段的构造函数,没有 private String type;

Lombok 停止在 1.16.20 版本的构造函数上生成 @ConstructorProperties(参见 changelog),因为它可能会破坏 Java 9+ 使用模块的应用程序。该注释包含构造函数参数的名称(它们在编译 class 时被删除,因此这是一种变通方法,以便在运行时仍然可以检索参数名称)。因为现在默认不生成注释,Jackson 无法将字段名称映射到构造函数参数。

解决方案一: 使用 @NoArgsConstructor@Setter,但您将失去不变性(如果这对您很重要)。

更新:@NoArgsConstructor@Getter(没有 @Setter)也可能有效(因为 INFER_PROPERTY_MUTATORS=true)。通过这种方式,您可以保持 class 不可变,至少对于常规(非反射)代码。

方案二: 配置 lombok 以再次生成注释,using a lombok.config file 包含行 lombok.anyConstructor.addConstructorProperties = true。 (如果您正在使用模块,请确保 java.desktop 在您的模块路径上。)添加 lombok.config 文件后清理并重新编译。

方案三: 将 Jackson 的构建器支持与 lombok 的 (@Jacksonized) @Builder/@SuperBuilder 结合使用,如 中所述。

方案四: 使用 javac(Java 8 及更高版本)编译时,将 -parameters 附加到命令。这会将构造函数和方法的参数名称存储在生成的 class 文件中,因此可以通过反射检索它们。

您想反序列化一个具有 final 字段的 class。所以你需要声明一个构造函数,其中包含要反序列化的最终字段。

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

并且当使用映射器反序列化时需要 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 注释,来自 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 {
   }
}

解决方案 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() {
        }
    }