不能让杰克逊和龙目岛合作

我正在尝试将杰克逊和龙目岛结合起来,这是我的课程:

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


}

这些是我添加到类中的 JAR:

我用 Netbeans 编译它(我不认为这是真正相关的,但我报告这无论如何使它完美和忠实可重现)。上面的五个 JAR 保存在项目文件夹中名为“ 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。

我的项目编译得很好。但是,它在执行时抛出一个异常。这个异常似乎不是那些看起来容易或者明显可以修复的东西。下面是输出,包括 stacktrace:

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注释,但是我不能使它更好。

我谷歌了异常和 找到了一份关于杰克逊的旧窃听器报告另一个是开放的,但似乎与其他东西有关。但是,这仍然没有说明这个 bug 是什么或者如何修复它。而且,我在别的地方找不到任何有用的东西。

由于我正在尝试做的是对 Lombok 和 jackson 的非常基本的使用,所以我找不到更多关于如何解决这个问题的有用信息似乎很奇怪。也许我遗漏了什么?

除了说“ 别用龙目怪”或者“ 别用 Jackson”,有人知道怎么解决这个问题吗?

131100 次浏览

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

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

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

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

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

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

它与所有的龙目岛和杰克逊版本一起工作,至少有几年的时间。

例如:

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

就是这样。 龙目岛和杰克逊合作无间。

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

除了数据类之外,还应该正确配置 ObjectMapper。 在这种情况下,它可以使用 Parameter terNamesModule 配置,并设置 Fields 和 Creator Method 的可见性

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

那就应该如我们所愿。

不推荐使用 @AllArgsConstructor(suppressConstructorProperties = true)。定义 lombok.anyConstructor.suppressConstructorProperties=true(https://projectlombok.org/features/configuration)并将 POJO 的 Lombok 注释从 @Value更改为 @Data + @NoArgsConstructor + @AllArgsConstructor

如果您希望使用 lombok 和 jackson 实现不可变但 json 可序列化 POJO。 在你的龙目录构建器 @JsonPOJOBuilder(withPrefix = "")上使用 jacksons 新注释 我试过这个方法,效果很好。 样本用量

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注释删除空的构建器类。

我在让龙目岛不要添加 ConstructorProperies注释的问题上遇到了麻烦,所以我就另辟蹊径,让杰克逊无法查看那个注释。

罪魁祸首是 FindCreator 注释注意:

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

还要注意 SetstructorPropertiesImpliesCreator:

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

因此有两个选项,要么将 MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES设置为 false,要么创建一个 JacksonAnnotationIntrospectorsetConstructorPropertiesImpliesCreator设置为 false,并通过 SetAnnotationintrspector将这个 AnnotationIntrospector设置为 ObjectMapper

请注意,我使用的是 Jackson 2.8.10,在这个版本中 MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES不存在。我不确定它是在哪个版本的杰克逊中添加的。因此,如果它不在那里,使用 JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator机制。

您还需要拥有这个模块。 Https://github.com/fasterxml/jackson-modules-java8

然后为编译器打开参数标志。

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

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

永恒 + 龙目岛 + 杰克逊可以通过下面的方式实现:

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

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

@Data
@AllArgsConstructor
public class MyPojo {


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

我也为此挣扎了一会儿,但是在这里查看文档 < a href = “ https://projectlombok.org/Features/實验/onX”rel = “ nofollow noReferrer”> 我可以看到 onConstruction 注释参数被认为是实验性的,在我的 IDE (STS4)中不受很好的支持。根据 Jackson 文档,私有成员在缺省情况下不(反)序列化。有很多快速的方法可以解决这个问题。

添加 JsonAutoDetect 注释并适当地设置它来检测受保护/私有成员

@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();
}
}

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

来自 Jan Rieke 的回答

从 Lombok 1.18.4开始,您就可以配置将注释复制到哪些地方 把这个插入到你的 lombok.config:

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

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

...

即使名称匹配,您也需要在每个字段上使用@JsonProperty,但无论如何,这都是一个很好的实践。您还可以使用这个函数将字段设置为 public final,我比较喜欢使用 getter。

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

不过,它也应该可以使用 getters (+ setter)。

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

private boolean isAggregate;

结果是抛出以下错误

Exception: Unrecognized field "isAggregate" (class

Lambok 将 isAggregate转换为 isAggregate()作为读取器,从而在内部将属性转换为 Lombok 作为 aggregate而不是 isAggregate。杰克逊图书馆不喜欢它,它需要 isAggregate属性代替。

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

索尔:

private Boolean isAggregate;

档号: https://www.baeldung.com/lombok-getter-boolean

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

我在我的春季启动应用程序中添加了这个

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

还有 Maven 中的依赖关系,我解决了所有问题。我不需要修改我的 Lombok 注释 Pojos

对我有效的选择

  • 只要在我的 bean 中添加@AllArgsConstruction,就可以实现这个功能。
  • 添加 mapper.configure (MapperFeature.ACCEPT _ CASE _ INSENSITIVE _ PROPERTIES,true) ; 对象映射器实例。

我发现两个选择来解决这个问题,如果你想使用 @Builder与杰克逊。

选择一

  • 添加私有默认 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;
}

选择二

感谢 这个的文章。

杰克逊预计,构建器方法将以类似于 .withProperty(...)的方式启动,但龙目岛将生成 .property(...)

您可以自己创建生成器类,以便向其中添加 Jackson 注释。然后,龙目岛将重用这个类,并将所有构建器方法添加到其中。

@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 {
}
}
  • 你需要做一些体力劳动
  • 还是比自己写建造者好多了
  • 还要注意,构建器上还有其他属性,比如 @JsonIgnorePropertie

另一个缺点是重构不会自动重命名 MyDtoBuilder。我希望在未来的龙目岛/杰克逊版本中,这个问题能得到解决。

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

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

要求:

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,这就是您如何保证 class-client 始终只依赖于具有不可变实体的构造函数的方法。将 @AllArgsConstructor它可能导致传递空值。

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

@NoArgsConstructor(force = true)

下面是一个使用 @Jacksonized 注释:

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


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

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

以上的答案对我来说都不起作用,但是下面的答案起作用了。

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

试试这个:

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