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

14 浏览
0 Comments

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

我喜欢根据这篇文章(为什么对象必须是不可变的)来使我的对象不可变。

然而,我正在尝试使用Jackson对象映射器解析对象。最初我遇到了JsonMappingException: No suitable constructor found for type [simple type, class ]: cannot instantiate from JSON object.

我可以按这里所述进行修复,提供一个默认构造函数并使我的字段非final。

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

有什么好的编程风格可以解决这个问题吗?唯一的解决方法是使我的对象可变吗?

我能不能使用不使用默认构造函数的不同映射器呢?

admin 更改状态以发布 2023年5月21日
0
0 Comments

太长不想读?使用 lombok 并避免默认构造函数

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

示例:

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


详细解答

有一个更好的替代方式来使用带有 @JsonCreator 注解的静态工厂方法,那就是拥有一个包含所有元素的构造函数(对于不可变类,这是必需的)。将其注释为 @JsonCreator,并将所有参数用 @JsonProperty 注释,如下所示:

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

@JsonCreator 注解提供了这些选项。它在文档中这样描述它们:

  • 只有一个带有 JsonProperty 注释的构造方法或工厂方法,用于参数:如果是这样的话,这被称为“委托创建者”,在这种情况下,Jackson 首先将 JSON 绑定到参数的类型,然后调用创建者。这通常与 JsonValue(用于序列化)配合使用。
  • 对于每个参数都使用 JsonProperty 或 JacksonInject 进行注释的构造方法/工厂方法,以指示要绑定的属性名称

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

此外,所有的JsonProperty注解必须指定实际名称(不是空字符串"default"),除非你使用其中一个扩展模块,可以检测参数名称。这是因为默认的JDK 8之前的版本无法从字节码中存储和/或检索参数名称。但是使用JDK 8(或使用像Paranamer这样的辅助库,或其他JVM语言,如Scala或Kotlin),指定名称是可选的。此外,版本为1.18.3或更高版本的lombok也可以与此良好地协作,其中您可以将lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty添加到lombok.config中,因此将其复制JsonProperty注解到构造函数,只要您确实使用它注释了所有字段(在我看来应该这样做)。要在构造函数上使用@JsonCreator注解,您可以使用实验性的onX功能。对于不可变数据类,您可以使用lombok的@Value,您的DTO可能看起来像这样(未经测试):

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

0
0 Comments

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

class School {
    //fields
    public School(String id, String name) {
        this.schoolId = id;
        this.schoolName = name;
    }
    @JsonCreator
    public static School create(Map object) {
        return new School((String) object.get("schoolId"), 
                          (String) object.get("schoolName"));
    }
    //getters
}

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

我认为您的问题是寻找 Jackson 解决方案,而不是新的模式/风格。

0