【问题标题】:How to deserialize a class with overloaded constructors using JsonCreator如何使用 JsonCreator 反序列化具有重载构造函数的类
【发布时间】:2013-04-02 14:28:46
【问题描述】:

我正在尝试使用 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 反序列化具有重载构造函数的类?

谢谢

【问题讨论】:

  • 正如答案指出的那样,不,您必须指定一个且唯一的构造函数。在您的情况下,请留下带有多个参数的那个,这样就可以了。 “缺失”参数将采用 null(对于对象)或默认值(对于原语)。
  • 谢谢。不过,允许多个构造函数将是一个不错的功能。实际上,我的例子有点做作。我尝试使用的对象实际上具有完全不同的参数列表,一个是正常创建的,另一个是使用 Throwable 创建的......我会看看我能做什么,也许有一个空的构造函数和 getter / setter可投掷
  • 是的,我相信它会很好,但规则可能会因不同的排列而变得相当复杂。总是可以为新功能、特性提交 RFE。

标签: java json jackson


【解决方案1】:

编辑:看哪,在 Jackson 的维护者的 blog post 中,似乎 2.12 可能会看到构造函数注入方面的改进。 (本次编辑时的当前版本是 2.11.1)

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


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

Jackson @JsonCreator annotation 2.5 javadocJackson annotations documentation 语法(构造函数s 和工厂方法s)让人相信确实可以标记多个构造函数。

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

查看标识创建者的代码,Jackson CreatorCollector 似乎忽略了重载的构造函数,因为它仅checks the first argument of the constructor

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 是那种动态的,那么也许应该考虑使用委托构造函数来比使用多个带注释的构造函数更优雅地处理负载变化。

另请注意,Jackson orders creators by priority,例如在此代码中:

// 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 只会使用 delegate 构造函数 Phone(Map&lt;String, Object&gt; properties),这意味着永远不会使用 Phone(@JsonProperty("value") String value)

【讨论】:

  • 恕我直言,这应该是公认的答案,因为它提供了完整的解释和很好的例子
【解决方案2】:

如果我得到了你想要达到的目标,你可以在没有构造函数重载的情况下解决它

如果您只想将空值放入 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 solution 对我不起作用。

【讨论】:

  • 最佳答案恕我直言
【解决方案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");
            }
        }
    }
}

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2016-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-19
      • 1970-01-01
      • 2011-07-09
      • 1970-01-01
      相关资源
      最近更新 更多