带有 Jackson 的不可变 Lombok 注释类

Posted

技术标签:

【中文标题】带有 Jackson 的不可变 Lombok 注释类【英文标题】:Immutable Lombok annotated class with Jackson 【发布时间】:2018-10-04 14:03:37 【问题描述】:

创建类的首选方法是什么

不可变 可以用 Jackson 序列化/反序列化 人类可读且样板级别低

最好,我希望这样的工作:

@Data(onConstructor = @__(@JsonCreator))

然后将所有字段设为private final。然而,这甚至没有编译(我不知道为什么)。使用

@AllArgsConstructor(onConstructor = @__(@JsonCreator))

会编译,但只会产生

InvalidDefinitionException: No serializer found for class

【问题讨论】:

【参考方案1】:

您可以使用 Lombok 的 @Builder 注释为您的不可变 POJO 类生成构建器。 但是让 Jackson 的反序列化可以使用 Lombok 生成的构建器有点棘手。

你需要用@JsonDeserialize(builder = ...)注释你的POJO类 告诉 Jackson 要使用哪个构建器类。 您需要使用@JsonPOJOBuilder(withPrefix = "") 注释构建器类 告诉 Jackson 它的 setter 方法with 开头。

例子:

一个不可变的 POJO 类:

@Data
@Builder(builderClassName = "PointBuilder")
@JsonDeserialize(builder = Point.PointBuilder.class)
public class Point 

    private final int x;

    private final int y;

    @JsonPOJOBuilder(withPrefix = "")
    public static class PointBuilder 
        // Lombok will add constructor, setters, build method
    

这是一个验证序列化/反序列化的 JUnit 测试:

public class PointTest extends Assert 

    private ObjectMapper objectMapper = new ObjectMapper();

    @Test
    public void testSerialize() throws IOException 
        Point point = new Point(10, 20);
        String json = objectMapper.writeValueAsString(point);
        assertEquals("\"x\":10,\"y\":20", json);
    

    @Test
    public void testDeserialize() throws IOException 
        String json = "\"x\":10,\"y\":20";
        Point point = objectMapper.readValue(json, Point.class);
        assertEquals(new Point(10, 20), point);
    

【讨论】:

我已经使用这种方法有一段时间了,它非常非常有效。一个非常小的改进是我总是将我的构建器类命名为 _Builder。这样,当我复制粘贴以创建新类等时......我不必记住更改builderClassName中的字符串。我曾经调用类 Builder(不带下划线),但是当您使用内部静态类执行此操作时,这与 @Builder 注释类有奇怪的冲突。【参考方案2】:

另一个不那么冗长的替代方案:

@Data
@Setter(AccessLevel.NONE)
public class Clazz 
    private String field;
 

当然,您仍然可以有一些直接修改字段的私有方法,但在 @Data POJO 中甚至不太可能有任何实际代码,因此希望不会发生这种情况。

免责声明: 这将产生不让常规 Java 代码创建对象的副作用(也许是有益的),因为只有一个没有修改器的默认构造函数。为了允许正常构建,您还需要 2 个注释:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Setter(AccessLevel.NONE)
public class Clazz 
    private String field;
 

【讨论】:

【参考方案3】:

添加 ConstructorProperties

创建一个lombok.config 文件in an appropriate location,其中包含以下行: lombok.anyConstructor.addConstructorProperties = true 将 lombok @Value 注释添加到您的类以使其不可变

然后 Jackson 的序列化和反序列化按预期工作。

这个方法:

符合标准 比以前的最佳答案少了样板 适用于 v1.16.20 (January 9th, 2018) 及更高版本

编辑:2020-08-16

注意:将@Builder@Value 一起使用会导致此解决方案失败。 (感谢下面@guilherme-blanco 的评论。) 但是,如果您还添加例如@AllArgsConstructor 它仍然按预期工作。

编辑:2021-08-19

注意:当您添加或更改lombok.config 文件时,除非您进行重建(清理然后构建),否则不会获取更改。我已经被这个发现了好几次了。 @Jacksonized annotation solution 是另一种方法,可以为注释的特定类实现预期结果。但是,我个人更喜欢不需要记住对用于反序列化的每个类进行注释。使用 lombok.config 可以消除这种开销。

【讨论】:

注意:此方法不能与 @Builder 结合使用。【参考方案4】:

通过引用answer by Joseph K. Strauss,我想出了以下解决方案。

对我有用的普通 lombok 注释看起来像这样。以下注释为您提供了具有生成器的不可变类,可以由 Jackson 序列化和反序列化。

    @Data
    @Setter(AccessLevel.NONE)
    @Builder(toBuilder = true)
    @AllArgsConstructor
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    public class Clazz 
        private String field;
     

我更喜欢这个解决方案,因为它不需要额外的Jackson specific annotations,也不需要额外的lombok specific files

【讨论】:

谢谢。但似乎:【参考方案5】:

Thomas Fritsch 的回答在 pom 中添加 Jackson Dataformat 依赖项后与 Spring Boot 完美配合。

@Data
@Builder(builderClassName = "PointBuilder")
@JsonDeserialize(builder = Point.PointBuilder.class)
public class Point 

    private final int x;

    private final int y;

    @JsonPOJOBuilder(withPrefix = "")
    public static class PointBuilder 
        // Lombok will add constructor, setters, build method
    


<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

【讨论】:

【参考方案6】:

为您的 pojo 尝试以下注释集:

@Value
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@AllArgsConstructor

【讨论】:

【参考方案7】:

我刚刚用这种方法解决了:

@Value
@Builder(setterPrefix = "with")
@JsonDeserialize(builder = Clazz.ClazzBuilder.class)
public class Clazz 
    private String field;

在这种情况下,您必须使用像 withField(...) 这样的构建器方法,这是 jackson 使用的默认行为。

【讨论】:

【参考方案8】:

自 2020 年 10 月 15 日 (Lombok v1.18.16) 起,您应该可以使用 @Jacksonized 注释。

@Jacksonized @Builder
@JsonIgnoreProperties(ignoreUnknown = true)
public class JacksonExample 
  private List<Foo> foos;

如链接文档中所述,此注释:

将 Jackson 配置为使用构建器进行反序列化, 将特定于字段的配置从带注释的类复制到生成的构建器(例如@JsonIgnoreProperties),并且 将生成器方法中使用的 Jackson 前缀(例如 builder().withField(field)builder.field(field) 与 Lombok 中配置的前缀对齐。

【讨论】:

我们可以把这个答案放在第一位吗?它就像一个魅力:)【参考方案9】:

为 Jackson 配置不可变类的最简单方法是使用 lombok 注解:@Value@Jacksonized

    @Jacksonized
    @Builder
    @Value
    class Foo 

        
    

【讨论】:

以上是关于带有 Jackson 的不可变 Lombok 注释类的主要内容,如果未能解决你的问题,请参考以下文章

Lombok + Jackson 不可变

带有杰克逊 JsonProperty 的 Lombok 构建器模式

Jackson 私有构造函数,JDK 9+,Lombok

java Builder 类中的附加方法(lombok 注释)

带有继承的@Data注释lombok上的警告等于/哈希码

Java Jackson将空字段反序列化为POJO中的默认空列表