使用新记录时无法反序列化 类

Unable to deserialize when using new Record classes

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

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.a.a.Post (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

我知道错误是说记录没有构造函数,但据我所知,记录 class 在后台处理它,相关的 getter 也在后台设置(不完全是 getter但是 id() title() 等等没有 get 前缀)。是因为Spring还没有采用最新的Java14记录吗?请指教。谢谢。

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

以下作品使用常用的 POJO。

Post班级

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

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

新记录class。

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

编译器为 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();
}

在这里你可以看到没有默认的构造函数需要得到杰克逊。您使用的构造函数是一个紧凑的构造函数,

public Post {
 }

您可以将 default/no args 构造函数定义为,

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

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


编辑为 PSA:Jackson can properly serialize and deserialize records as of 2.12 which has been released

一些 Jackson 注释可能会导致 Jackson 使用字段而不是 getter。仍然比 Java 14 class(没有 Lombok 或类似解决方案)更简洁。

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

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

Declaration annotations are permitted on record components if they are applicable to record components, parameters, fields, or methods. Declaration annotations that are applicable to any of these targets are propagated to implicit declarations of any mandated members.

另请参阅:When is the @JsonProperty property used and what is it used for?

也可以利用@JsonAutoDetect

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

如果将 Objectmapper 配置为在全局范围内使用字段可见性,则不需要 class 级别的注释。

另请参阅:How to specify jackson to only use fields - preferably globally

示例:

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

If a public accessor method or (non-compact) canonical constructor is declared explicitly, then it only has the annotations which appear on it directly; nothing is propagated from the corresponding record component to these members.

来自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 在构造函数上。使用此设置的真实 class 示例。

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

这是预定用于 jackson 2.12

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