【问题标题】:How to deserialize Java Optional field with lombok builder?如何使用 lombok builder 反序列化 Java 可选字段?
【发布时间】:2020-07-17 03:07:55
【问题描述】:

我想反序列化 lombok 构建器中的 Java 可选字段。下面是我的代码

@JsonDeserialize(builder = AvailabilityResponse.Builder.class)
@Getter
@ToString
@EqualsAndHashCode
@Builder(setterPrefix = "with", builderClassName = "Builder", toBuilder = true)
public class AvailabilityResponse {

    private final List<Train> trainDetails;
    private final String name;
    private final Optional<String> detail;

    public static class Builder {

        @JacksonXmlProperty(localName = "TRAIN")
        private List<Train> trainDetails;

        @JacksonXmlProperty(localName = "NAME")
        private String name;

        @JacksonXmlProperty(localName = "DETAIL_TRAIN", isAttribute = true)
        private String detail;

        public AvailabilityResponse build() {
            return new AvailabilityResponse(
                trainDetails, // I have field validation here. if null or empty throwing Exception
                name, // I have field validation here. if null or empty throwing Exception
                Optional.ofNullable(detail)); // This is Optional field
        }
    }   

}    

如果我像下面这样覆盖构建器方法,则能够反序列化

    private Optional<String> name; // this is static builder class field

        @JacksonXmlProperty(localName = "DETAIL_TRAIN", isAttribute = true)
        public Builder detail(final String theDetail) {
            this.detail = Optional.ofNullable(theDetail);
            return this;
        }   

我在@Builder 中使用了 setterPrefix ="with"。但是如果我用“with”前缀覆盖上面的方法,它就不起作用了。

请有人帮我实现这一目标

【问题讨论】:

  • 请显示你的xml。
  • 123455nsmr23 @杰森

标签: java jackson optional lombok jackson-databind


【解决方案1】:

Jackson 使用其jackson-datatype-jdk8 模块支持Optional。您可以简单地将其添加到 pom.xml 中的 &lt;dependencies&gt; 部分:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
    <version>2.10.3</version>
</dependency>

然后,按如下方式初始化您的映射器:

ObjectMapper mapper = new XmlMapper().registerModule(new Jdk8Module());

该映射器将自动检测Optionals:如果XML字段有值,它将被包裹在Optional中;如果 XML 字段为空(即&lt;DETAIL_TRAIN/&gt;),则结果将为Optional.empty()。 (在您的情况下,您有一个属性;那些不能为空。)

Jackson 将 XML 中不存在的字段或属性映射到 null(或者更准确地说,它根本不调用 setter,因此字段值仍将是默认值)。在这种情况下,如果您还希望有一个空的Optional,则需要将字段默认设置为Optional.empty(),并将@Builder.Default 添加到该字段。

最后,lombok 可以自动将您的注释从字段复制到生成的构建器。您需要使用项目根目录中的 lombok.config 文件告诉 lombok 要复制哪些注释:

lombok.copyableAnnotations += com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
config.stopBubbling = true

这意味着您根本不需要自定义构建器类:

@JsonDeserialize(builder = AvailabilityResponse.Builder.class)
@Getter
@ToString
@EqualsAndHashCode
@Builder(setterPrefix = "with", builderClassName = "Builder", toBuilder = true)
public class AvailabilityResponse {

    @JacksonXmlProperty(localName = "TRAIN")
    @JsonProperty("TRAIN")
    private final List<Train> trainDetails;

    @JacksonXmlProperty(localName = "NAME")
    @JsonProperty("NAME")
    private final String name;

    @JacksonXmlProperty(localName = "DETAIL_TRAIN", isAttribute = true)
    @JsonProperty("DETAIL_TRAIN")
    @Builder.Default
    private final Optional<String> detail = Optional.empty();
}

请注意,由于a Jackson bug,您还需要在字段上添加@JsonProperty 注释。

如果您想验证字段值,您应该在构造函数中执行此操作,而不是在构建器中。通过这种方式,您还将捕获那些不使用构建器的实例。为此,只需自己实现全参数构造函数; build() 方法会自动使用它:

private AvailabilityResponse(List<Train> trainDetails, String name, Optional<String> detail) {
    this.trainDetails = Objects.requireNonNull(trainDetails);
    this.name = Objects.requireNonNull(name);
    this.detail = Objects.requireNonNull(detail);
}

我建议创建这个构造函数private,因为

  • 它强制您的类的用户使用构建器,并且
  • 在公共 API 中使用 Optionals 作为参数是一种不好的风格。

【讨论】:

  • 我想我找到了问题所在:Jackson 在将@JacksonXmlProperty 注解放在参数上时无法识别它。我认为这是杰克逊的一个错误,因为注释是允许在参数上的,它适用于@JsonProperty
  • github.com/FasterXML/jackson-dataformat-xml/issues/384。解决方法是在该字段上也放置一个 @JsonProperty。我更新了我的答案。
  • 添加@JsonProperty 后它正在工作。但它看起来很奇怪,因为注释和属性名称添加了两次。此外,如果我在非可选字段中有 null,我想抛出 IllegalArguementException,为此我只有覆盖静态构建器类。
  • 我更新了我的答案,说明为什么你不应该在构建器中验证,我描述了一个解决方案。
  • 我不明白您为什么必须删除 @Builder.Default 和默认值分配。 @Builder.Default 无论如何都会将该默认值赋值移动到一个单独的方法中,因此在调用手动构造函数时不会发生对 detail 的赋值。
猜你喜欢
  • 2021-05-29
  • 2023-03-03
  • 1970-01-01
  • 2014-01-11
  • 2020-11-28
  • 1970-01-01
  • 2020-09-06
  • 2014-08-10
  • 2019-05-24
相关资源
最近更新 更多