繁体   English   中英

不能让 Jackson 和 Lombok 一起工作

[英]Can't make Jackson and Lombok work together

我正在尝试结合 Jackson 和 Lombok。 这些是我的课:

package testelombok;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.experimental.Wither;

@Value
@Wither
@AllArgsConstructor(onConstructor=@__(@JsonCreator))
public class TestFoo {
    @JsonProperty("xoom")
    private String x;
    private int z;
}
package testelombok;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xebia.jacksonlombok.JacksonLombokAnnotationIntrospector;
import java.io.IOException;

public class TestLombok {

    public static void main(String[] args) throws IOException {
        TestFoo tf = new TestFoo("a", 5);
        System.out.println(tf.withX("b"));
        ObjectMapper om = new ObjectMapper().setAnnotationIntrospector(new JacksonLombokAnnotationIntrospector());
        System.out.println(om.writeValueAsString(tf));
        TestFoo tf2 = om.readValue(om.writeValueAsString(tf), TestFoo.class);
        System.out.println(tf2);
    }

}

这些是我添加到 classpth 中的 JARs:

我正在用 Netbeans 编译它(我认为这并不真正相关,但无论如何我都会报告它以使其完美且忠实地重现)。 上面的五个 JARs 保存在项目文件夹内名为“ lib ”的文件夹中(以及“ src ”、“ nbproject ”、“ test ”和“ build ”)。 我通过项目属性中的“添加 JAR/文件夹”按钮将它们添加到 Netbeans 中,它们按上面列表的确切顺序列出。 该项目是一个标准的“Java 应用程序”类型的项目。

此外,Netbeans 项目配置为“保存时不编译”、“生成调试信息”、“报告弃用的 API ”、“跟踪 java 依赖项”、“激活注释处理”和“在编辑器中激活注释处理”。 Netbeans 中没有明确配置注释处理器或注释处理选项。 此外,“ -Xlint:all ”命令行选项在编译器命令行中传递,编译器在外部 VM 上运行。

我的 javac 的版本是 1.8.0_72,我的 java 的版本是 1.8.0_72-b15。 我的 Netbeans 是 8.1。

我的项目编译得很好。 但是,它在执行过程中会引发异常。 例外似乎不是任何看起来容易或明显可修复的东西。 这是 output,包括堆栈跟踪:

TestFoo(x=b, z=5)
{"z":5,"xoom":"a"}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Argument #0 of constructor [constructor for testelombok.TestFoo, annotations: {interface java.beans.ConstructorProperties=@java.beans.ConstructorProperties(value=[x, z]), interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
 at [Source: {"z":5,"xoom":"a"}; line: 1, column: 1]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:296)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:269)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
    at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
    at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:475)
    at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3890)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3785)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2833)
    at testelombok.TestLombok.main(TestLombok.java:14)
Caused by: java.lang.IllegalArgumentException: Argument #0 of constructor [constructor for testelombok.TestFoo, annotations: {interface java.beans.ConstructorProperties=@java.beans.ConstructorProperties(value=[x, z]), interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addDeserializerConstructors(BasicDeserializerFactory.java:511)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:323)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:253)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:219)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:141)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:406)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:352)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
    ... 7 more

我已经尝试过使用@Value@AllArgsConstructor注释随机戳,但我无法让它变得更好。

我用谷歌搜索了这个异常,发现了一个关于 jackson 的旧错误报告另一个是打开的,但似乎与其他东西有关 但是,这仍然没有说明这个错误是什么或如何修复它。 另外,我在其他地方找不到任何有用的东西。

由于我要做的是对 lombok 和 jackson 的非常基本的用法,因此我找不到任何有关如何解决此问题的有用信息似乎很奇怪。 也许我错过了什么?

除了说“不要使用lombok”或“不要使用jackson ”之外,有人知道如何解决这个问题吗?

如果您想要不可变但使用 lombok 和 jackson 的 json 可序列化 POJO。 在您的 lomboks 构建器上使用 jacksons 新注释@JsonPOJOBuilder(withPrefix = "")我尝试了这个解决方案,效果很好。 示例使用

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;

@JsonDeserialize(builder = Detail.DetailBuilder.class)
@Value
@Builder
public class Detail {

    private String url;
    private String userName;
    private String password;
    private String scope;

    @JsonPOJOBuilder(withPrefix = "")
    public static class DetailBuilder {

    }
}

如果您有太多带有@Builder的类并且您不希望样板代码为空注释,则可以覆盖注释拦截器以使withPrefix为空

mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
        @Override
        public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) {
            if (ac.hasAnnotation(JsonPOJOBuilder.class)) {//If no annotation present use default as empty prefix
                return super.findPOJOBuilderConfig(ac);
            }
            return new JsonPOJOBuilder.Value("build", "");
        }
    });

您可以使用@JsonPOJOBuilder注释删除空的构建器类。

Immutable + Lombok + Jackson 可以通过以下方式实现:

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Value;

@Value
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class LocationDto {

    double longitude;
    double latitude;
}

class ImmutableWithLombok {

    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();

        String stringJsonRepresentation = objectMapper.writeValueAsString(new LocationDto(22.11, 33.33));
        System.out.println(stringJsonRepresentation);

        LocationDto locationDto = objectMapper.readValue(stringJsonRepresentation, LocationDto.class);
        System.out.println(locationDto);
    }
}

我尝试了以上几种,它们都是喜怒无常的。 真正对我有用的是我在这里找到的答案。

在项目的根目录中添加一个lombok.config文件(如果您还没有完成)

lombok.config

并在里面粘贴这个

lombok.anyConstructor.addConstructorProperties=true

然后你可以像下面这样定义你的 pojo:

@Data
@AllArgsConstructor
public class MyPojo {

    @JsonProperty("Description")
    private String description;
    @JsonProperty("ErrorCode")
    private String errorCode;
}

下面是一个使用@Jacksonized注解的例子:

import lombok.Value;
import lombok.Builder;
import lombok.extern.jackson.Jacksonized;

@Jacksonized
@Builder
@Value
public class User {
    private final String name;
    private final String surname;
}

它确实需要您使用@Builder注释。

我遇到了完全相同的问题,通过添加suppressConstructorProperties = true参数(使用您的示例)“解决”了它:

@Value
@Wither
@AllArgsConstructor(suppressConstructorProperties = true)
public class TestFoo {
    @JsonProperty("xoom")
    private String x;
    private int z;
}

Jackson 显然不喜欢添加到构造函数的java.beans.ConstructorProperties注释。 suppressConstructorProperties = true参数告诉Lombok不要添加它(默认情况下会添加)。

如果您想将@Builder与 Jackson 一起使用,我找到了两个解决此问题的选项。

选项1

  • 添加私有默认 noArgs 和 allArgs 构造函数。
@Builder
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Person {

    @JsonProperty("user_name")
    private String name;
}

选项 2

感谢这篇文章。

Jackson 期望构建器方法像.withProperty(...)一样开始,但 Lombok 会生成.property(...)

您可以做的是自己创建构建器类,以便您可以向其添加 Jackson 注释。 然后,Lombok 将重新使用这个类并将所有构建器方法添加到它。

@JsonDeserialize(builder = MyDto.MyDtoBuilder.class)
@Builder
@Getter
public class MyDto {

    @JsonProperty("user_id")
    private String userId;

    @JsonPOJOBuilder(withPrefix = "")
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class MyDtoBuilder {
    }
}
  • 你需要做一些手工工作
  • 还是比自己写 Builder 要好得多
  • 另请注意,诸如@JsonIgnorePropertie之类的其他属性会出现在构建器上

另一个缺点是重构不会自动重命名MyDtoBuilder 我希望在未来的 Lombok/Jackson 版本中解决这个问题。

更新:我找到了另一种解决方案(使用 lombok 1.18.20 和 spring boot 2.4.5 测试),作为选项 1 添加。

@AllArgsConstructor(suppressConstructorProperties = true)已弃用。 定义lombok.anyConstructor.suppressConstructorProperties=truehttps://projectlombok.org/features/configuration )并将 POJO 的 lombok 注释从@Value@Data + @NoArgsConstructor + @AllArgsConstructor为我工作。

来自 Jan Rieke 的回答

从 lombok 1.18.4 开始,您可以配置将哪些注解复制到构造函数参数。 将其插入您的lombok.config

 lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty

然后只需将@JsonProperty添加到您的字段中:

...

即使名称匹配,您也需要在每个字段上都有一个 @JsonProperty,但无论如何这是一个很好的做法。 您还可以使用它将您的字段设置为 public final,我更喜欢它而不是 getters。

@ToString
@EqualsAndHashCode
@Wither
@AllArgsConstructor(onConstructor=@__(@JsonCreator))
public class TestFoo {
    @JsonProperty("xoom")
    public final String x;
    @JsonProperty("z")
    public final int z;
}

不过,它也应该与 getter (+setters) 一起使用。

对我来说,当我将 lombok 版本更新为:'org.projectlombok:lombok:1.18.0'

它可以做得更简单,不需要额外的注释,问题可能出在继承上,即子类也应该是可反序列化的。 所以,我的例子:

要求:

lombok.config在项目根目录中,正文包含:

lombok.anyConstructor.addConstructorProperties=true
/** The parent class **/

@Value
@NonFinal
@SuperBuilder
@RequiredArgsConstructor
public class Animal {
  String name;
}

/** The child class **/

@Value
@SuperBuilder
@RequiredArgsConstructor
public class Cat {
  Long tailLength;
  
  @ConstructorProperties({"tailLength", "name})
  public Cat(Long tailLength, String name) {
      super(name);
      this.tailLength = tailLength;
  }
}

它:

  1. 允许构建对象,包括父字段
  2. 使用默认的ObjectMapper和 Jackson 进行序列化/反序列化
  3. 父类和子类的实例是不可变的

我对其他例子的建议:

  1. 尽量不要将自定义注释放在特定的类上,这会使它变得不均匀 无论如何,总有一天你会找到一个通用的解决方案。
  2. 尽量不要将Jackson注释放在构造函数的任何字段上,它会创建耦合,当 Jackson 能够在没有任何注释的情况下进行序列化/反序列化时。
  3. 不要将@AllArgsConstructor用于不可变实体。 当您的类只有最终字段时,概念上正确的是@RequiredArgsConstructor ,这就是您保证类客户端始终仅依赖于具有不可变实体的构造函数的方式。 @AllArgsConstructor可能会导致传递空值。

如果你使用它的“mixin”模式,你可以让 Jackson 玩任何东西。 基本上,它为您提供了一种将 Jackson 注释添加到现有类的方法,而无需实际修改该类。 我倾向于在这里推荐它而不是 Lombok 解决方案,因为这解决了杰克逊在杰克逊功能方面遇到的问题,因此它更有可能长期工作。

我的所有课程都这样注释:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
@Data
@Accessors(fluent = true)
@NoArgsConstructor
@AllArgsConstructor

它与所有 Lombok 和 Jackson 版本一起工作了至少几年。

例子:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
@Data
@Accessors(fluent = true)
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    String id;
    String first;
    String last;
}

就是这样。 龙目岛和杰克逊一起玩耍就像一个魅力。

我建议您使用 Gson,因为它不会给您带来所有这些麻烦。

我在我的 Spring Boot 应用程序中添加了这个

spring.mvc.converters.preferred-json-mapper=gson

连同 maven 中的依赖关系,我解决了所有问题。 我不需要修改我的龙目岛注释 pojos

我已经设法保持我的类不可变,并通过使用这个 lombok 注释来反序列化它们:

@NoArgsConstructor(force = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class Person {
   String id;
   String first;
   String last;
}

除了数据类之外,还应该正确配置 ObjectMapper。 在这种情况下,可以使用 ParameterNamesModule 配置,并设置字段和创建者方法的可见性

    om.registerModule(new ParameterNamesModule());
    om.setVisibility(FIELD, JsonAutoDetect.Visibility.ANY);
    om.setVisibility(CREATOR, JsonAutoDetect.Visibility.ANY);

然后它应该按预期工作。

我在让 Lombok 不添加ConstructorProperies注释时遇到问题,所以换了一种方式并禁止杰克逊查看该注释。

罪魁祸首是JacksonAnnotationIntrospector.findCreatorAnnotation 注意:

if (_cfgConstructorPropertiesImpliesCreator
            && config.isEnabled(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES)

还要注意JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator

public JacksonAnnotationIntrospector setConstructorPropertiesImpliesCreator(boolean b)
{
    _cfgConstructorPropertiesImpliesCreator = b;
    return this;
}

所以有两个选项,要么将MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES设置为 false,要么创建JacksonAnnotationIntrospectorsetConstructorPropertiesImpliesCreator设置为false并通过ObjectMapper.setAnnotationIntrospector将此AnnotationIntrospector设置到ObjectMapper中。

请注意几件事,我使用的是 Jackson 2.8.10,并且在该版本中MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES不存在。 我不确定它是在哪个版本的 Jackson 中添加的。 因此,如果它不存在,请使用JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator机制。

你也需要有这个模块。 https://github.com/FasterXML/jackson-modules-java8

然后为您的编译器打开 -parameters 标志。

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>

我也为此挣扎了一会儿。 但是通过查看此处的文档,我可以看到 onConstructor 注释参数被认为是实验性的,并且在我的 IDE(STS 4)上没有得到很好的支持。 根据杰克逊文档,默认情况下私有成员不会(反)序列化。 有一些快速的方法可以解决这个问题。

添加 JsonAutoDetect 注释并对其进行适当设置以检测受保护/私有成员。 这对 DTO 来说很方便

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class SomeClass

添加带有 @JsonCreator 注释的工厂函数,如果您需要一些对象验证或额外的转换,这最有效。

public class SomeClass {

   // some code here

   @JsonCreator
   public static SomeClass factory(/* params here dressing them in @JsonProperty annotations*/) {
      return new SomeClass();
   }
}

当然你也可以自己手动添加构造函数。

尝试添加@NoArgsConstructor

对我有用的选项

  • 只需在我的 bean 中添加 @AllArgsConstructor 即可。
  • 添加 mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); 对象映射器实例。

以上答案都不适合我,但下面的答案确实有效。

发生的情况是 Jackson 不支持流利的吸气剂,但是您可以告诉它使用反射来读取字段。

尝试这个:

@Value
@Accessors(chain = true, fluent = true)
@Builder(builderClassName = "Builder")
public static class TestFoo {
  // ...
}

var foo = ...
var writer = new ObjectMapper()
    .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
    .writer();

writer.writeValueAsString(foo);

我有一个不同的问题,它与布尔原始类型有关。

private boolean isAggregate;

结果引发了以下错误

Exception: Unrecognized field "isAggregate" (class 

Lambok 将isAggregate转换为isAggregate()作为 getter,在内部将属性转换为 lombok 作为aggregate而不是isAggregate Jackson 库不喜欢它,它需要isAggregate属性。

我将原始布尔值更新为 Wrapper Boolean 以解决此问题。 如果您正在处理boolean类型,还有其他选项可供您选择,请参阅下面的参考。

溶胶:

private Boolean isAggregate;

参考: https ://www.baeldung.com/lombok-getter-boolean

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM