【问题标题】:How to use Jackson to deserialize external Lombok builder class如何使用 Jackson 反序列化外部 Lombok 构建器类
【发布时间】:2019-06-18 10:01:36
【问题描述】:

我有一个第 3 方 Lombok 构建器 POJO,我无法修改它,我想使用 jackson 对其进行序列化。值得注意的是它没有有一个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 类位于外部库中。

将这些注释应用到 mixin 没有任何效果。

@JsonDeserialize(ExternalClass.ExternalClassBuilder.class)
public abstract class ExternalClassMixin {
   @JsonPOJOBuilder(withPrefix="")
   public static ExternalClassBuilder {
   }
} 

我发现唯一可行的方法是利用 @Builder 创建的包访问 AllArgsConstructor 并使用以下构造函数填充 mixin

public abstract class ExternalClassMixin {
   @JsonCreator public ExternalClassMixin(
      @JsonProperty("name") String name,
      @JsonProperty("data") String data,
      // etc.
  ) {} 
} 

这显然是不可取的,因为它需要显式地对每个类属性进行迭代和硬编码,这使得 mixin 对于外部 POJO 中的任何更改都很脆弱。

我的问题是 - 是否有一种健壮、可维护的方法来使用 Jackson 序列化这个外部构建器类而不用修改它,使用 mixin 或完整的反序列化器?

更新

我实现了@jan-rieke 的出色回答,包括使用反射来寻找内部构建器类的建议。

...
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);
   }
}

【问题讨论】:

  • 你的问题解决了我的问题。我可以访问 JTwig 用来生成 API 客户端的外部 lombok 构建器。我可以将注释 @JsonDeserialize@JsonPOJOBuilder 添加到模板中。如果您对此做出回答,我将奖励您。

标签: java jackson mixins builder lombok


【解决方案1】:

这可以通过创建两个 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"}

【讨论】:

    【解决方案2】:

    您可以按如下方式自定义您的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 的反序列化使用其构建器,并且
    • 将构建器设置方法的默认前缀设置为""@JsonPOJOBuilder 注释存在时除外)。

    如果您不想在findPOJOBuilder() 中明确列出所有外部类,您当然可以通过编程方式查看该类以检查它是否具有看起来像构建器的内部类。

    【讨论】:

    • 感谢您的回答。它奏效了,我按照您的建议用通用解决方案替换了硬编码的类名。
    猜你喜欢
    • 2020-09-06
    • 2021-05-29
    • 2021-06-29
    • 1970-01-01
    • 2015-02-09
    • 2020-11-28
    • 1970-01-01
    • 2012-11-18
    • 1970-01-01
    相关资源
    最近更新 更多