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

时间:2018-09-21 05:45:56

标签: java jackson immutability

我希望基于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;
}

为克服该问题,我应该遵循什么良好的编程风格?唯一的解决方法是使我的对象可变吗?

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

2 个答案:

答案 0 :(得分:6)

您可以使用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将使用create版本的json调用Map方法。这有效地解决了这个问题。

我相信您的问题是在寻找一种Jackson解决方案,而不是一种新的模式/风格。

答案 1 :(得分:1)

有一个用@JsonCreator注释的静态工厂方法的imo更好的替代方法,该方法具有所有元素的构造函数(无论如何,不​​可变类都是必需的)。用@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注释为您提供了这些选项。它在其文档中这样描述它们:

  
      
  • 不带JsonProperty注释的单参数构造函数/工厂方法:如果是这样,则这就是所谓的“委托创建者”,在这种情况下,Jackson首先将JSON绑定到参数的类型中,然后调用creator。这通常与JsonValue(用于序列化)结合使用。
  •   
  • 构造器/工厂方法,其中每个参数均用JsonProperty或JacksonInject注释,以指示要绑定的属性的名称
  •   

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

  

还请注意,除非您使用可以检测参数名称的扩展模块之一,否则所有JsonProperty批注都必须指定实际名称(“ default”不为空字符串)。这是因为8之前的默认JDK版本无法存储和/或从字节码中检索参数名称。但是对于JDK 8(或使用诸如Paranamer之类的帮助程序库,或诸如Scala或Kotlin之类的其他JVM语言),指定名称是可选的。

或者,这也可以在龙目岛版本1.18.3或更高版本中很好地工作,您可以在其中将lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty添加到lombok.config并因此将JsonProperty注释复制到构造函数,因为您确实要用它注释所有字段(无论如何,imo都应该这样做)。要将@JsonCreator注释放在构造函数上,可以使用实验性的onX feature。将lombok的@Value用于不可变的数据类,则DTO可能看起来像这样(未经测试):

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