如何使用ObjectMapper在没有默认构造函数的情况下反序列化不可变对象?


106

我想使用com.fasterxml.jackson.databind.ObjectMapper序列化和反序列化不可变对象。

不可变类如下所示(只有3个内部属性,获取器和构造函数):

public final class ImportResultItemImpl implements ImportResultItem {

    private final ImportResultItemType resultType;

    private final String message;

    private final String name;

    public ImportResultItemImpl(String name, ImportResultItemType resultType, String message) {
        super();
        this.resultType = resultType;
        this.message = message;
        this.name = name;
    }

    public ImportResultItemImpl(String name, ImportResultItemType resultType) {
        super();
        this.resultType = resultType;
        this.name = name;
        this.message = null;
    }

    @Override
    public ImportResultItemType getResultType() {
        return this.resultType;
    }

    @Override
    public String getMessage() {
        return this.message;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

但是,当我运行此单元测试时:

@Test
public void testObjectMapper() throws Exception {
    ImportResultItemImpl originalItem = new ImportResultItemImpl("Name1", ImportResultItemType.SUCCESS);
    String serialized = new ObjectMapper().writeValueAsString((ImportResultItemImpl) originalItem);
    System.out.println("serialized: " + serialized);

    //this line will throw exception
    ImportResultItemImpl deserialized = new ObjectMapper().readValue(serialized, ImportResultItemImpl.class);
}

我得到这个异常:

com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class eu.ibacz.pdkd.core.service.importcommon.ImportResultItemImpl]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: {"resultType":"SUCCESS","message":null,"name":"Name1"}; line: 1, column: 2]
    at 
... nothing interesting here

这个异常要求我创建一个默认的构造函数,但这是一个不可变的对象,所以我不想拥有它。它将如何设置内部属性?这会完全混淆API的用户。

所以我的问题是:我可以在没有默认构造函数的情况下以某种方式反序列化不可变对象吗?


在进行反序列化时,反序列化器不了解您的任何构造函数,因此会命中默认构造函数。因此,您必须创建一个默认构造函数,该构造函数不会更改不变性。我也看到班级不是最后的,为什么呢?任何人都可以覆盖功能不是吗?
Abhinaba Basu 2015年

上课不是最后的课程,因为我忘了在那儿写,感谢您的注意:)
Michal

Answers:


149

要让Jackson知道如何创建对象以进行反序列化,请为您的构造函数使用@JsonCreator@JsonProperty注释,如下所示:

@JsonCreator
public ImportResultItemImpl(@JsonProperty("name") String name, 
        @JsonProperty("resultType") ImportResultItemType resultType, 
        @JsonProperty("message") String message) {
    super();
    this.resultType = resultType;
    this.message = message;
    this.name = name;
}

1
+1感谢谢尔盖(Sergey)。它确实起作用。但是即使使用JsonCreator注释后,我仍然遇到问题。但是经过一番回顾,我意识到我忘记了在资源中使用RequestBody注释。现在可以正常工作了。
拉胡尔

4
如果由于无法访问POJO而无法添加默认构造函数或@JsonCreatorand @JsonProperty注释怎么办?还有办法反序列化对象吗?
杰克·斯特劳

1
@JackStraw 1)对象的子类,并覆盖所有getter和setter。2)为该类编写自己的序列化器/反序列化器并使用它(搜索“自定义序列化器” 3)包装对象并为一个属性编写一个序列化器/反序列化器(这可能比为序列化器编写序列化器少做些工作类本身,但也许不是)。
ErikE

+1您的评论救了我!到目前为止,我发现的每个解决方案都是创建默认的构造函数...
Nilson Aguiar

34

您可以使用私有的默认构造函数,然后Jackson将通过反射来填充字段,即使它们是私有的final。

编辑:如果您有继承,请对父类使用受保护/受包保护的默认构造函数。


仅以此答案为基础,如果要反序列化的类扩展了另一个类,则可以使超类(或两个类)的默认构造函数受到保护。
KWILLIAMS

3

Sergei Petunin的第一个答案是正确的。但是,我们可以通过删除构造函数的每个参数上的多余@JsonProperty注释来简化代码。

可以通过将com.fasterxml.jackson.module.paramnames.ParameterNamesModule添加到ObjectMapper中来完成:

new ObjectMapper()
        .registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))

(顺便说一句:默认情况下,此模块是在SpringBoot中注册的。如果您使用JacksonObjectMapperConfiguration中的ObjectMapper bean,或者如果您使用bean Jackson2ObjectMapperBuilder创建自己的ObjectMapper,则可以跳过该模块的手动注册。)

例如:

public class FieldValidationError {
  private final String field;
  private final String error;

  @JsonCreator
  public FieldValidationError(String field,
                              String error) {
    this.field = field;
    this.error = error;
  }

  public String getField() {
    return field;
  }

  public String getError() {
    return error;
  }
}

并且ObjectMapper反序列化此json,没有任何错误:

{
  "field": "email",
  "error": "some text"
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.