不能让杰克逊和龙目岛一起工作

Posted

技术标签:

【中文标题】不能让杰克逊和龙目岛一起工作【英文标题】:Can't make Jackson and Lombok work together 【发布时间】:2017-01-15 19:52:05 【问题描述】:

我正在尝试将 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 中的 JAR:

龙目岛:https://projectlombok.org/downloads/lombok.jar(版本 1.16.10)

Jackson 注释:http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.8.2/jackson-annotations-2.8.2.jar

杰克逊核心:http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.8.2/jackson-core-2.8.2.jar

杰克逊数据绑定:http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.8.2/jackson-databind-2.8.2.jar

杰克逊-龙目岛:http://repo1.maven.org/maven2/io/paradoxical/jackson-lombok/1.1/jackson-lombok-1.1.jar

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

此外,Netbeans 项目配置为“保存时不编译”、“生成调试信息”、“报告已弃用的 API ", "track java dependencies", "activacte annotation proccessing" 和 "activacte annotation proccessing in the editor"。 Netbeans 中没有显式配置注释处理器或注释处理选项。另外,“-Xlint:all”命令行选项在编译器命令行中传递,编译器运行在外部VM上。

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

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

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 注释随机戳,但我无法让它变得更好。

我用谷歌搜索了异常和found an old bug report on jackson 和another one that is open, but seems to be related to something else。但是,这仍然没有说明这个错误是什么或如何修复它。另外,我在其他地方找不到任何有用的东西。

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

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

【问题讨论】:

【参考方案1】:

如果您想要不可变但使用 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 注释的空构建器类。

【讨论】:

欢迎提供解决方案的链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted. 此解决方案在解决 Jackson 的基于脑死亡构造函数的反序列化问题的同时保持不变性(多字段构造函数在每个构造函数参数上都需要 @JsonProperty,而不是尝试一些智能的东西)。 (我试过 onConstructor_=@JsonCreator 没有运气)【参考方案2】:

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);
    

【讨论】:

AllArgsConstructor 不是已经是@Value 注释的一部分了吗? 显式添加@NoArgsConstructor 会覆盖@Value 注释生成的构造函数,因此您必须添加额外的@AllArgsConstructor 我发现的最佳解决方案 - 在我看来 - 没有 Builder 模式。谢谢。 是否为我们在构造函数中添加@AllArgsConstructor“是否”@JsonCreator?如果是,为什么我们需要@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) 这是我一直在寻找的东西!回复最后一条评论,你需要他们两个,这就是为什么需要@NoArgsConstructor,但是如果你不初始化字段(force=true)你会在设置@NoArgsConstructor注释时出错,因为字段是final 【参考方案3】:

我尝试了上述几种方法,它们都很喜怒无常。 真正对我有用的是我找到的答案here。

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

lombok.config

然后在里面粘贴这个

lombok.anyConstructor.addConstructorProperties=true

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

@Data
@AllArgsConstructor
public class MyPojo 

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

【讨论】:

但这不是可变的吗? 不可变,只需通过 Getter 和 Builder 或 RequiredArgsConstructor 更改数据并将所有字段标记为 final 关于为什么这在我的情况下不起作用的任何想法?我有一个 spring boot 应用程序,我已将 lombok.config 放在我的 src 中,但您的解决方案仍然无法正常工作。 这个配置并没有解决我的问题,至少在我的单元测试中没有。【参考方案4】:

我遇到了完全相同的问题,通过添加 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 不要添加它(默认情况下会添加)。

【讨论】:

suppressConstructorProperties 现在已弃用 :-( 这不是问题,因为新的默认值也是false【参考方案5】:

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

【讨论】:

这会将类更改为可变的,我认为这不是 OP 想要的【参考方案6】:

From Jan Rieke's Answer

从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 (+setter)。

【讨论】:

这解决了我的问题!太感谢了!我一直被困在 JSON 和 POJO 之间的 spring-boot-integration 和序列化上,并得到错误:Can not deserialize instance of java.lang.String out of START_OBJECT token... 这解决了它!我需要为每个构造函数参数添加 @JsonProperty 注释,并且添加到 @AllArgsConstructor 允许 lombok 对其进行检测。【参考方案7】:

如果您想将@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

感谢this 文章。

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 添加。

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

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

要求:

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;
  

它:

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

我对其他示例的建议:

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

【讨论】:

我找到的最好的解决方案,至少看起来更清晰【参考方案10】:

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

【讨论】:

【参考方案11】:

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

@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;

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

【讨论】:

这是可变类。【参考方案12】:

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

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

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

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

【讨论】:

Lombok 团队创建了 @Jacksonized 注释,它生成了 Jackson 所需的所有代码,它以更简洁的方式解决了我的所有问题,节省了大量时间。终于!【参考方案13】:

这是一个使用示例 @Jacksonized注解:

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

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

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

【讨论】:

【参考方案14】:
@JsonInclude(JsonInclude.Include.NON_NULL)
@Data
public class Person 
   String id;
   String first;
   String last;

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

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

然后它应该按预期工作。

【讨论】:

【参考方案15】:

我在让 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,要么创建一个JacksonAnnotationIntrospector,将setConstructorPropertiesImpliesCreator 设置为false,然后通过ObjectMapper.setAnnotationIntrospector 将此AnnotationIntrospector 设置为ObjectMapper

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

【讨论】:

【参考方案16】:

你也需要有这个模块。 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>

【讨论】:

【参考方案17】:

我也为此苦苦挣扎了一会儿。但是查看文档here 我可以看到 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();
   

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

【讨论】:

【参考方案18】:

对我有用的选项

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

【讨论】:

【参考方案19】:

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

@NoArgsConstructor(force = true)

【讨论】:

【参考方案20】:

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

private boolean isAggregate;

结果抛出以下错误

Exception: Unrecognized field "isAggregate" (class 

Lambok 将 isAggregate 转换为 isAggregate() 作为吸气剂,在内部将属性设置为 aggregate 而不是 isAggregate。 Jackson 库不喜欢它,它需要 isAggregate 属性。

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

索尔:

private Boolean isAggregate;

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

【讨论】:

您是否尝试过使用聚合而不是原始类型的 isAggregate?我相信,它也可以解决错误。另请参阅***.com/a/42620096/6332074

以上是关于不能让杰克逊和龙目岛一起工作的主要内容,如果未能解决你的问题,请参考以下文章

龙目岛没有创建默认构造函数导致杰克逊数据绑定失败[重复]

杰克逊不能反序列化空数组

Lombok + Jackson 不可变

不能让 QTcpSocket/QTcpServer 一起工作

NSWindow 和 setLevel,不能让“顶部”功能正常工作

Spring启动 - 会话作用域组件的setter不能使用单件服务 - 字段为空