如何使用 Jackson 反序列化外部 Lombok 构建器 class
How to use Jackson to deserialize external Lombok builder class
我有一个第 3 方 Lombok 构建器 POJO,我无法修改它,我想使用 jackson 对其进行序列化。值得注意的是,它 not 有一个 NoArgsConstructor。
@Data
@Builder
public class ExternalClass {
private String name;
private String data;
// etc.
}
从表面上看,这似乎很简单,但在实践中却令人难以置信地沮丧,因为每个可能的选择似乎都被不同的并发症所抵消。本质上,我无法让 external Lombok builder 与 jackson mixin 一起工作。
Lombok 生成 .name(String name)
风格的流畅设置器,而 Jackson 的内置构建器反序列化器期望 .withName(String name)
。 Lombok 文档和其他地方的食谱(例如 here)建议在预先声明的内部存根构建器上结合使用 @JsonDeserialize(builder=ExternalClass.ExternalClassBuilder.class)
和 @JsonPOJOBuilder(withPrefix="")
。但这是不可能的,因为 Lombok class 在外部库中。
将这些注释应用于混入没有任何效果。
@JsonDeserialize(ExternalClass.ExternalClassBuilder.class)
public abstract class ExternalClassMixin {
@JsonPOJOBuilder(withPrefix="")
public static ExternalClassBuilder {
}
}
我发现唯一可行的方法是利用 @Builder
创建的包访问 AllArgsConstructor 并使用以下构造函数填充混入
public abstract class ExternalClassMixin {
@JsonCreator public ExternalClassMixin(
@JsonProperty("name") String name,
@JsonProperty("data") String data,
// etc.
) {}
}
这显然是不可取的,因为它需要明确地对每个 class 属性 进行迭代和硬编码,使得 mixin 容易受到外部 POJO 中的任何更改的影响。
我的问题是 - 是否有一种健壮的、可维护的方法来使用 Jackson 序列化这个外部构建器 class 而无需修改它,使用 mixin 或者可能是一个完整的反序列化器?
更新
我实现了@jan-rieke 的出色答案,包括使用反射寻找内部构建器的建议 class。
...
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
Class<?> innerBuilder;
try {
innerBuilder = Class.forName(ac.getName()+"$"+ac.getRawType().getSimpleName()+"Builder");
log.info("Builder found: {}", ac.getName());
return innerBuilder;
} catch( ClassNotFoundException e ) {
return super.findPOJOBuilder(ac);
}
}
您可以按如下方式自定义 ObjectMapper
:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
@Override
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
if (ExternalClass.class.equals(ac.getRawType())) {
return ExternalClass.ExternalClassBuilder.class;
}
return super.findPOJOBuilder(ac);
}
@Override
public Value findPOJOBuilderConfig(AnnotatedClass ac) {
if (ac.hasAnnotation(JsonPOJOBuilder.class)) {
return super.findPOJOBuilderConfig(ac);
}
return new JsonPOJOBuilder.Value("build", "");
}
});
这将
- 明确配置
ExternalClass
的反序列化使用其构建器,并且
- 将构建器 setter 方法的默认前缀设置为
""
(存在 @JsonPOJOBuilder
注释时除外)。
如果您不想在 findPOJOBuilder()
中明确列出所有外部 classes,您当然可以通过编程方式查看 class 以检查它是否具有内部 class 看起来像个建筑师。
这可以通过创建两个 mixin 来实现:一个用于 ExternalClass
(指定要使用的构建器),一个用于 ExternalClass.ExternalClassBuilder
(指定构建器方法中缺少前缀)。
@JsonDeserialize(builder = ExternalClass.ExternalClassBuilder.class)
public interface ExternalClassMixin {
}
@JsonPOJOBuilder(withPrefix="")
public interface ExternalClassBuilderMixin {
}
这会以所需方式序列化和反序列化 JSON:
String json = "{\"name\": \"The Name\", \"data\": \"The Data\"}";
ObjectMapper mapper = new ObjectMapper()
.addMixIn(ExternalClass.class, ExternalClassMixin.class)
.addMixIn(ExternalClass.ExternalClassBuilder.class, ExternalClassBuilderMixin.class);
System.out.println(mapper.readValue(json, ExternalClass.class));
System.out.println(mapper.writeValueAsString(mapper.readValue(json, ExternalClass.class)));
输出:
ExternalClass(name=The Name, data=The Data)
{"name":"The Name","data":"The Data"}
我有一个第 3 方 Lombok 构建器 POJO,我无法修改它,我想使用 jackson 对其进行序列化。值得注意的是,它 not 有一个 NoArgsConstructor。
@Data
@Builder
public class ExternalClass {
private String name;
private String data;
// etc.
}
从表面上看,这似乎很简单,但在实践中却令人难以置信地沮丧,因为每个可能的选择似乎都被不同的并发症所抵消。本质上,我无法让 external Lombok builder 与 jackson mixin 一起工作。
Lombok 生成 .name(String name)
风格的流畅设置器,而 Jackson 的内置构建器反序列化器期望 .withName(String name)
。 Lombok 文档和其他地方的食谱(例如 here)建议在预先声明的内部存根构建器上结合使用 @JsonDeserialize(builder=ExternalClass.ExternalClassBuilder.class)
和 @JsonPOJOBuilder(withPrefix="")
。但这是不可能的,因为 Lombok class 在外部库中。
将这些注释应用于混入没有任何效果。
@JsonDeserialize(ExternalClass.ExternalClassBuilder.class)
public abstract class ExternalClassMixin {
@JsonPOJOBuilder(withPrefix="")
public static ExternalClassBuilder {
}
}
我发现唯一可行的方法是利用 @Builder
创建的包访问 AllArgsConstructor 并使用以下构造函数填充混入
public abstract class ExternalClassMixin {
@JsonCreator public ExternalClassMixin(
@JsonProperty("name") String name,
@JsonProperty("data") String data,
// etc.
) {}
}
这显然是不可取的,因为它需要明确地对每个 class 属性 进行迭代和硬编码,使得 mixin 容易受到外部 POJO 中的任何更改的影响。
我的问题是 - 是否有一种健壮的、可维护的方法来使用 Jackson 序列化这个外部构建器 class 而无需修改它,使用 mixin 或者可能是一个完整的反序列化器?
更新
我实现了@jan-rieke 的出色答案,包括使用反射寻找内部构建器的建议 class。
...
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
Class<?> innerBuilder;
try {
innerBuilder = Class.forName(ac.getName()+"$"+ac.getRawType().getSimpleName()+"Builder");
log.info("Builder found: {}", ac.getName());
return innerBuilder;
} catch( ClassNotFoundException e ) {
return super.findPOJOBuilder(ac);
}
}
您可以按如下方式自定义 ObjectMapper
:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
@Override
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
if (ExternalClass.class.equals(ac.getRawType())) {
return ExternalClass.ExternalClassBuilder.class;
}
return super.findPOJOBuilder(ac);
}
@Override
public Value findPOJOBuilderConfig(AnnotatedClass ac) {
if (ac.hasAnnotation(JsonPOJOBuilder.class)) {
return super.findPOJOBuilderConfig(ac);
}
return new JsonPOJOBuilder.Value("build", "");
}
});
这将
- 明确配置
ExternalClass
的反序列化使用其构建器,并且 - 将构建器 setter 方法的默认前缀设置为
""
(存在@JsonPOJOBuilder
注释时除外)。
如果您不想在 findPOJOBuilder()
中明确列出所有外部 classes,您当然可以通过编程方式查看 class 以检查它是否具有内部 class 看起来像个建筑师。
这可以通过创建两个 mixin 来实现:一个用于 ExternalClass
(指定要使用的构建器),一个用于 ExternalClass.ExternalClassBuilder
(指定构建器方法中缺少前缀)。
@JsonDeserialize(builder = ExternalClass.ExternalClassBuilder.class)
public interface ExternalClassMixin {
}
@JsonPOJOBuilder(withPrefix="")
public interface ExternalClassBuilderMixin {
}
这会以所需方式序列化和反序列化 JSON:
String json = "{\"name\": \"The Name\", \"data\": \"The Data\"}";
ObjectMapper mapper = new ObjectMapper()
.addMixIn(ExternalClass.class, ExternalClassMixin.class)
.addMixIn(ExternalClass.ExternalClassBuilder.class, ExternalClassBuilderMixin.class);
System.out.println(mapper.readValue(json, ExternalClass.class));
System.out.println(mapper.writeValueAsString(mapper.readValue(json, ExternalClass.class)));
输出:
ExternalClass(name=The Name, data=The Data)
{"name":"The Name","data":"The Data"}