13

我正在尝试查看是否可以用 Java 14 中的新 Record 类替换现有的 Pojos。但无法这样做。收到以下错误:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException:无法构造实例com.a.a.Post(没有创建者,如默认构造,存在):无法从对象值反序列化(没有基于委托或属性的创建者)

我知道错误是说记录没有构造函数,但是从我看到的记录类在后台处理它并且相关的吸气剂也在后台设置(不完全是吸气剂,而是 id() title() 等等在没有 get 前缀的情况下)。是因为 Spring 还没有采用最新的 Java 14 记录吗?请指教。谢谢。

我在 Spring Boot 版本 2.2.6 中执行此操作并使用 Java 14。

以下工作使用通常的 POJO。

邮政类

public class PostClass {
    private int userId;
    private int id;
    private String title;
    private String body;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

调用休息服务的方法现在可以使用,因为我正在使用上面的 POJO。

public PostClass[] getPosts() throws URISyntaxException {
    String url = "https://jsonplaceholder.typicode.com/posts";
    return template.getForEntity(new URI(url), PostClass[].class).getBody();
}

但是,如果我切换到使用记录的位置,则会收到上述错误。

新的记录类。

public record Post(int userId, int id, String title, String body) {
}

更改方法以使用记录而不是失败。

public Post[] getPosts() throws URISyntaxException {
    String url = "https://jsonplaceholder.typicode.com/posts";
    return template.getForEntity(new URI(url), Post[].class).getBody();
}

编辑:

尝试将如下构造函数添加到记录 Post 和相同的错误:

public record Post(int userId, int id, String title, String body) {
    public Post {
    }
}

或者

public record Post(int userId, int id, String title, String body) {
    public Post(int userId, int id, String title, String body) {
        this.userId = userId;
        this.id = id;
        this.title = title;
        this.body = body;
    }
}
4

4 回答 4

13

一些 Jackson Annotations 可能会导致 Jackson 使用字段而不是 getter。仍然远没有 Java 14 之前的类(没有 Lombok 或类似解决方案)那么冗长。

record Foo(@JsonProperty("a") int a, @JsonProperty("b") int b){
}

这可能有效,因为根据https://openjdk.java.net/jeps/359

如果声明注释适用于记录组件、参数、字段或方法,则允许在记录组件上使用声明注释。适用于任何这些目标的声明注释被传播到任何授权成员的隐式声明。

另请参阅:@JsonProperty 属性何时使用以及它的用途是什么?

也可以利用@JsonAutoDetect

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
record Bar(int a, int b){
}

如果将 Objectmapper 配置为全局使用字段 Visibility,则不需要此类级别的注释。

另请参阅:如何指定杰克逊仅使用字段 - 最好是全局

例子:

public class Test {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper om = new ObjectMapper();
        System.out.println(om.writeValueAsString(new Foo(1, 2)));  //{"a":1,"b":2}
        System.out.println(om.writeValueAsString(new Bar(3, 4)));  //{"a":3,"b":4} 
    }

    record Foo(@JsonProperty("a") int a, @JsonProperty("b") int b){
    }

    @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
    record Bar(int a, int b){
    }
}

该功能还有一个 Github 问题:https ://github.com/FasterXML/jackson-future-ideas/issues/46

于 2020-04-25T16:25:10.653 回答
3

这是为杰克逊 2.12 准备的

https://github.com/FasterXML/jackson-future-ideas/issues/46

于 2020-09-06T05:28:42.580 回答
1

如果显式声明了公共访问器方法或(非紧凑)规范构造函数,则它只有直接出现在其上的注释;没有任何东西从相应的记录组件传播到这些成员。

来自https://openjdk.java.net/jeps/384

所以添加

new ObjectMapper().registerModules(new ParameterNamesModule())

并尝试

@JsonCreator record Value(String x);

或类似的东西

record Value(String x) {

@JsonCreator
public Value(String x) {
this.x = x;
}
}

或一直到

record Value(@JsonProperty("x") String x) {

@JsonCreator
public Value(@JsonProperty("x") String x) {
this.x = x;
}
}

这就是我如何让 lombok 和 jackson 的不可变 pojos 工作,我不明白为什么记录不能在相同的格式下工作。我的设置是 Jackson 参数名称模块,java 8 的 -parameters 编译器标志(我不认为这对于 jdk9+ 是必需的),构造函数上的 @JsonCreator。使用此设置的真实类的示例。

@Value
@AllArgsConstructor(onConstructor_ = @JsonCreator)
public final class Address {

  private final String line1;

  private final String line2;

  private final String city;

  private final String region;

  private final String postalCode;

  private final CountryCode country;
}
于 2020-08-27T22:04:38.710 回答
1

编译器为 Record 生成构造函数和其他访问器方法。

在你的情况下,

  public final class Post extends java.lang.Record {  
  public Post(int, int java.lang.String, java.lang.String);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int userId();
  public int id();
  public java.lang.String title();
  public java.lang.String body();
}

在这里,您可以看到 Jackson 不需要默认构造函数。您使用的构造函数是一个紧凑的构造函数,

public Post {
 }

您可以将默认/无参数构造函数定义为,

public record Post(int userId, int id, String title, String body) {
    public Post() {
        this(0,0, null, null);
    }
}

但是杰克逊使用 Getter 和 Setter 来设置值。所以简而言之,您不能使用 Record 来映射响应。


编辑为 PSA:Jackson 可以正确序列化和反序列化已发布的2.12 记录。

于 2020-04-10T13:35:07.070 回答