如何为不可变 class 创建默认构造函数

How to create default constructor for immutable class

我喜欢根据 this article (Why objects must be immutable).

让我的对象不可变

但是,我正在尝试使用 Jackson Object Mapper 解析一个对象。我最初得到 JsonMappingException: No suitable constructor found for type [simple type, class ]: cannot instantiate from JSON object.

我可以通过提供默认构造函数并使我的字段成为非最终字段来修复它 here

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;

@AllArgsConstructor
// @NoArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Data
public class School {

    @NonNull
    private final String schoolId;

    @NonNull
    private final String schoolName;
}

我应该遵循什么好的编程风格来克服这个问题?唯一的解决办法是让我的对象可变吗?

我可以使用不使用默认构造函数的其他映射器吗?

您可以使用 Jackson 工厂(用 @JsonCreator 注释的方法)读取地图上的字段并调用您的非默认构造函数:

class School {
    //fields

    public School(String id, String name) {
        this.schoolId = id;
        this.schoolName = name;
    }

    @JsonCreator
    public static School create(Map<String, Object> object) {
        return new School((String) object.get("schoolId"), 
                          (String) object.get("schoolName"));
    }

    //getters
}

Jackson 将使用 json 的 Map 版本调用 create 方法。而这有效的解决了问题。

我相信您的问题是寻找 Jackson 解决方案,而不是新的 pattern/style。

TL;DR:使用 lombok 并避免使用默认构造函数

  • 使用@Value
  • 制作不可变数据class
  • @JsonProperty("name-of-property")
  • 注释所有字段
  • lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty 添加到您的 lombok.config 以将其复制到生成的构造函数中
  • 创建一个带有 @JsonCreator
  • 注释的全参数构造函数

示例:

@Value
@AllArgsConstructor(onConstructor_ = @JsonCreator)
class School {
    @JsonProperty("schoolId")
    String schoolId;
    @JsonProperty("schoolName")
    String schoolName;
}

长答案

在我看来,有一个比用 @JsonCreator 注释的静态工厂方法更好的替代方法,那就是为所有元素提供一个构造函数(无论如何,不​​可变的 classes 都需要它)。用 @JsonCreator 注释 that 并用 @JsonProperty 注释所有参数,如下所示:

class School {
    //fields

    @JsonCreator
    public School(
            @JsonProperty("id") String id,
            @JsonProperty("name") String name) {
        this.schoolId = id;
        this.schoolName = name;
    }

    //getters
}

这些是 @JsonCreator 注释为您提供的选项。它在文档中这样描述它们:

  • Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. This is often used in conjunction with JsonValue (used for serialization).
  • Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to

在某些情况下,您甚至可能不需要明确指定参数名称。有关 @JsonCreator 的文档进一步说明:

Also note that all JsonProperty annotations must specify actual name (NOT empty String for "default") unless you use one of extension modules that can detect parameter name; this because default JDK versions before 8 have not been able to store and/or retrieve parameter names from bytecode. But with JDK 8 (or using helper libraries such as Paranamer, or other JVM languages like Scala or Kotlin), specifying name is optional.

或者,这也适用于 lombok 版本 1.18.3 或更高版本,您可以在其中将 lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty 添加到 lombok.config,因此让它复制 JsonProperty 注释给构造函数,假设你确实用它注释了所有字段(imo 应该做什么)。要在构造函数上放置 @JsonCreator-注释,您可以使用实验性的 onX feature。将 lombok 的 @Value 用于不可变数据 classes,您的 DTO 可能看起来像这样(未经测试):

@Value
//@AllArgsConstructor(onConstructor = @__(@JsonCreator)) // JDK7 or below
@AllArgsConstructor(onConstructor_ = @JsonCreator) // starting from JDK8
class School {
    @JsonProperty("schoolId")
    String schoolId;
    @JsonProperty("schoolName")
    String schoolName;
}