如何使用 lombok 构建器反序列化 Java 可选字段?

How to deserialize Java Optional field with lombok builder?

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

@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" 前缀覆盖上面的方法,它就不起作用了。

请有人帮我实现这个

Jackson 使用其 jackson-datatype-jdk8 模块支持 Optional。您只需将它添加到 pom.xml:

中的 <dependencies> 部分
<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 字段为空(即 <DETAIL_TRAIN/>),则结果将为 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

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

@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,因为

  • 它强制您 class 的用户使用生成器,并且
  • 在您的 public API.
  • 中将 Optional 作为参数是错误的风格