使用Lombok注释的不可变类与Jackson

43

创建既是不可变的,又可以与Jackson进行序列化/反序列化,同时又易读且具有低水平样板的类,最佳方法是什么?

优先选择以下这种方式:

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

然后将所有字段设置为 private final。但是,这甚至无法编译(我不确定原因)。使用

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

将编译,但只会产生

InvalidDefinitionException: No serializer found for class
9个回答

60

添加ConstructorProperties:

  • 在适当的位置创建一个 lombok.config 文件,并写入以下行: lombok.anyConstructor.addConstructorProperties = true
  • 为您的类添加 lombok @Value 注解以使其不可变

然后,Jackson 对象序列化和反序列化将按预期工作。

这种方法:

编辑:2020-08-16

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

编辑:2021-08-19

  • 注意:当您添加或更改 lombok.config 文件时,除非进行重建(清除后再构建),否则不会使用更改。 我已经被这个问题折磨了几次。
  • @Jacksonized注解解决方案是另一种实现特定类的所需结果的方法。然而,我个人更喜欢不需要记住为每个用于反序列化的类做注解。使用lombok.config可以消除这种额外负担。

  • 3
    注意:此方法与@Builder不能同时使用。 - Guilherme Blanco

    43
    截至2020年10月15日(Lombok v1.18.16),您只需使用@Jacksonized注解即可。
    @Jacksonized @Builder
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class JacksonExample {
      private List<Foo> foos;
    }
    

    如链接文档所述,此注解:

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

    6
    我们能否将这个答案投票至第一名?它非常有效 :) - Pawel Dobierski

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

    示例:

    一个不可变的 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
        }
    }
    

    POJO outline

    这是一个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);
        }
    }
    

    3
    我已经使用这种方法有一段时间了,它非常有效。一个很小的改进是我总是将我的builder类命名为_Builder。这样当我复制粘贴创建新类时,我不需要记得更改builderClassName中的字符串。我过去称这些类为Builder(没有下划线),但当你在内部静态类中这样做时,会与@Builder注解类产生奇怪的冲突。 - Kevin Day

    6

    参考Joseph K. Strauss的答案,我得到了以下解决方案。

    对我有效的简单lombok注释看起来像这样。以下注释可以为您提供具有构建器的不可变类,这些类可以由Jackson进行序列化和反序列化。

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

    我更喜欢这个解决方案,因为它不需要额外的Jackson特定的注释和额外的lombok特定的文件


    谢谢这个。但似乎: - Breton F.

    3
    使用lombok注释:@Value@Jacksonized,是为Jackson配置不可变类的最简单方法:
        @Jacksonized
        @Builder
        @Value
        class Foo {
    
            
        }
    

    3

    另一个更为简洁的替代方案:

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

    当然,您仍然可以拥有一些直接修改字段的私有方法,但是在@Data POJO中实际上没有任何代码,因此这种情况很少发生。
    免责声明: 这将产生一个副作用(可能是有益的),即不允许常规Java代码创建对象,因为只有一个默认构造函数没有mutators。为了允许正常构建,您需要2个以上的注释。
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Setter(AccessLevel.NONE)
    public class Clazz {
        private String field;
    } 
    

    2
    我刚使用以下方法解决了这个问题:
    @Value
    @Builder(setterPrefix = "with")
    @JsonDeserialize(builder = Clazz.ClazzBuilder.class)
    public class Clazz {
        private String field;
    }
    

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

    2

    尝试使用以下注解来为您的POJO配置:

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

    0

    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>
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接