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
,并将 lombok
从 1.16.14
更新为 1.18.0
和 jackson-datatype-jsr310
从 2.9.4
到 2.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() {
}
}
更新后反序列化失败。
我将微服务从 Spring 1.5.10.RELEASE
更新为 Spring 2.0.3.RELEASE
,并将 lombok
从 1.16.14
更新为 1.18.0
和 jackson-datatype-jsr310
从 2.9.4
到 2.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
和 Jackson2.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() {
}
}