如何使用JsonCreator使用重载的构造函数反序列化类


82

我正在尝试使用Jackson 1.9.10反序列化此类的实例:

public class Person {

@JsonCreator
public Person(@JsonProperty("name") String name,
        @JsonProperty("age") int age) {
    // ... person with both name and age
}

@JsonCreator
public Person(@JsonProperty("name") String name) {
    // ... person with just a name
}
}

当我尝试这个我得到以下

冲突的基于属性的创建者:已经... {interface org.codehaus.jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator()}],遇到...,注释:{interface org.codehaus。 jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator()}]

有没有办法使用Jackson重载带有重载的构造函数的类?

谢谢


4
答案指出,不,您必须指定一个且唯一的构造函数。在您的情况下,保留带有多个参数的参数,这样就可以了。“缺少”参数将采用null(对于Objects)或默认值(对于图元)。
StaxMan

谢谢。不过,允许多个构造函数将是一个不错的功能。实际上,我的示例有些人为。我尝试使用的对象实际上具有完全不同的参数列表,一个通常创建,另一个使用Throwable创建...我将看到我能做些什么,也许有一个空的构造方法和getter / setter方法可
投掷

是的,我敢肯定会很好,但是规则随着不同的排列会变得相当复杂。始终可以提交RFE以获取新功能,新功能。
StaxMan

Answers:


118

尽管没有正确记录,但是每种类型只能有一个创建者。您可以在类型中具有任意数量的构造函数,但是只有其中一个应该在其@JsonCreator上具有注释。


69

编辑:看哪,在杰克逊维护者的博客文章中,似乎2.12可能会看到有关构造函数注入的改进。(此编辑时的当前版本为2.11.1)

改进构造函数创建者的自动检测,包括解决/缓解模棱两可的一参数构造函数的问题(委托与属性)


对于Jackson数据绑定2.7.0仍然适用。

杰克逊@JsonCreator注释2.5的javadoc杰克逊的注释文档语法(构造函数小号和工厂方法小号)让确实认为,人们可以标记多个构造。

标记批注,可用于将构造函数和工厂方法定义为用于实例化关联类的新实例的标记。

查看确定创建者的代码,Jackson似乎CreatorCollector忽略了重载的构造函数,因为它仅检查构造函数的第一个参数

Class<?> oldType = oldOne.getRawParameterType(0);
Class<?> newType = newOne.getRawParameterType(0);

if (oldType == newType) {
    throw new IllegalArgumentException("Conflicting "+TYPE_DESCS[typeIndex]
           +" creators: already had explicitly marked "+oldOne+", encountered "+newOne);
}
  • oldOne 是第一个确定的构造函数创建者。
  • newOne 是重载的构造函数创建者。

这意味着这样的代码将无法工作

@JsonCreator
public Phone(@JsonProperty("value") String value) {
    this.value = value;
    this.country = "";
}

@JsonCreator
public Phone(@JsonProperty("country") String country, @JsonProperty("value") String value) {
    this.value = value;
    this.country = country;
}

assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336"); // raise error here
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336");

但是这段代码会起作用:

@JsonCreator
public Phone(@JsonProperty("value") String value) {
    this.value = value;
    enabled = true;
}

@JsonCreator
public Phone(@JsonProperty("enabled") Boolean enabled, @JsonProperty("value") String value) {
    this.value = value;
    this.enabled = enabled;
}

assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\",\"enabled\":true}", Phone.class).value).isEqualTo("+336");

这有点古怪,可能无法证明未来


该文档对对象创建的工作方式含糊不清;从我从代码中收集的数据来看,可以混合使用不同的方法:

例如,可以有一个带有注释的静态工厂方法 @JsonCreator

@JsonCreator
public Phone(@JsonProperty("value") String value) {
    this.value = value;
    enabled = true;
}

@JsonCreator
public Phone(@JsonProperty("enabled") Boolean enabled, @JsonProperty("value") String value) {
    this.value = value;
    this.enabled = enabled;
}

@JsonCreator
public static Phone toPhone(String value) {
    return new Phone(value);
}

assertThat(new ObjectMapper().readValue("\"+336\"", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\",\"enabled\":true}", Phone.class).value).isEqualTo("+336");

它有效,但不理想。最后,这可能是有道理的,例如,如果json是动态的,那么也许应该考虑使用委托构造函数来处理有效载荷变化,而不是使用多个带注释的构造函数。

另请注意,杰克逊按优先级排序创建者,例如以下代码:

// Simple
@JsonCreator
public Phone(@JsonProperty("value") String value) {
    this.value = value;
}

// more
@JsonCreator
public Phone(Map<String, Object> properties) {
    value = (String) properties.get("value");
    
    // more logic
}

assertThat(new ObjectMapper().readValue("\"+336\"", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\",\"enabled\":true}", Phone.class).value).isEqualTo("+336");

这次Jackson不会引发错误,但是Jackson只会使用委托构造函数Phone(Map<String, Object> properties),这意味着Phone(@JsonProperty("value") String value)永远不会使用。


7
恕我直言,这应该是公认的答案,因为它提供了完整的解释并提供了很好的例子
matiou

7

如果我对您要实现的目标正确,则可以解决此问题而无需构造函数重载

如果仅要将空值放在JSON或Map中不存在的属性中,则可以执行以下操作:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
    private String name;
    private Integer age;
    public static final Integer DEFAULT_AGE = 30;

    @JsonCreator
    public Person(
        @JsonProperty("name") String name,
        @JsonProperty("age") Integer age) 
        throws IllegalArgumentException {
        if(name == null)
            throw new IllegalArgumentException("Parameter name was not informed.");
        this.age = age == null ? DEFAULT_AGE : age;
        this.name = name;
    }
}

当我找到您的问题时就是我的情况。我花了一些时间弄清楚如何解决它,也许那正是您想要做的。 @Brice解决方案对我不起作用。


1
最佳anwer imho
Jakob

3

如果您不介意做更多的工作,则可以手动反序列化实体:

@JsonDeserialize(using = Person.Deserializer.class)
public class Person {

    public Person(@JsonProperty("name") String name,
            @JsonProperty("age") int age) {
        // ... person with both name and age
    }

    public Person(@JsonProperty("name") String name) {
        // ... person with just a name
    }

    public static class Deserializer extends StdDeserializer<Person> {
        public Deserializer() {
            this(null);
        }

        Deserializer(Class<?> vc) {
            super(vc);
        }

        @Override
        public Person deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            JsonNode node = jp.getCodec().readTree(jp);
            if (node.has("name") && node.has("age")) {
                String name = node.get("name").asText();
                int age = node.get("age").asInt();
                return new Person(name, age);
            } else if (node.has("name")) {
                String name = node.get("name").asText();
                return new Person("name");
            } else {
                throw new RuntimeException("unable to parse");
            }
        }
    }
}
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.