简体   繁体   English

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

[英]How to deserialize a class with overloaded constructors using JsonCreator

I am trying to deserialize an instance of this class using Jackson 1.9.10:我正在尝试使用 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
}
}

When I try this I get the following当我尝试这个时,我得到以下信息

Conflicting property-based creators: already had ... {interface org.codehaus.jackson.annotate.JsonCreator @org.codehaus.jackson.annotate.JsonCreator()}], encountered ... , annotations: {interface org.codehaus.jackson.annotate.JsonCreator @org.codehaus.jackson.annotate.JsonCreator()}]冲突的基于属性的创建者:已经有 ... {interface org.codehaus.jackson.annotate.JsonCreator @org.codehaus.jackson.annotate.JsonCreator()}],遇到了 ... ,注解:{interface org.codehaus. jackson.annotate.JsonCreator @org.codehaus.jackson.annotate.JsonCreator()}]

Is there a way to deserialize a class with overloaded constructors using Jackson?有没有办法使用 Jackson 反序列化具有重载构造函数的类?

Thanks谢谢

Though its not properly documented, you can only have one creator per type.尽管没有正确记录,但每种类型只能有一个创建者。 You can have as many constructors as you want in your type, but only one of them should have a @JsonCreator annotation on it.您可以在类型中拥有任意@JsonCreator构造函数,但其​​中只有一个应该有@JsonCreator注释。

EDIT : Behold, in a blog post by the maintainers of Jackson, it seems 2.12 may see improvements in regard to constructor injection.编辑:看哪,在 Jackson 维护者的博客文章中,似乎 2.12 可能会看到构造函数注入方面的改进。 (Current version at the time of this edit is 2.11.1) (本次编辑时的当前版本为 2.11.1)

Improve auto-detection of Constructor creators, including solving/alleviating issues with ambiguous 1-argument constructors (delegating vs properties)改进构造函数创建者的自动检测,包括解决/缓解含糊不清的 1 参数构造函数(委托与属性)的问题


This still hold true for Jackson databind 2.7.0.这仍然适用于 Jackson databind 2.7.0。

The Jackson @JsonCreator annotation 2.5 javadoc or Jackson annotations documentation grammar ( constructor s and factory method s ) let believe indeed that one can mark multiple constructors.杰克逊@JsonCreator注释2.5的javadoc杰克逊的注释文档语法(构造函数S和工厂方法S)让确实相信,一个可以标记多个构造。

Marker annotation that can be used to define constructors and factory methods as one to use for instantiating new instances of the associated class.可用于将构造函数和工厂方法定义为用于实例化关联类的新实例的标记注释。

Looking at the code where the creators are identified, it looks like the Jackson CreatorCollector is ignoring overloaded constructors because it only checks the first argument of the constructor .查看识别创建者的代码,看起来 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 is the first identified constructor creator. oldOne是第一个确定的构造函数创建者。
  • newOne is the overloaded constructor creator. newOne是重载的构造函数创建者。

That means that code like that won't work这意味着这样的代码将不起作用

@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");

But this code will work :但是这段代码会起作用:

@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");

This is a bit hacky and may not be future proof .这有点老套,可能不是未来的证明


The documentation is vague about how object creation works;该文档对对象创建的工作方式含糊不清; from what I gather from the code though, it's that it is possible to mix different methods :不过,从我从代码中收集的信息来看,可以混合使用不同的方法:

For example one can have a static factory method annotated with @JsonCreator例如,可以有一个用@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");

It works but it is not ideal.它有效,但并不理想。 In the end, it could make sense, eg if the JSON is that dynamic then maybe one should look to use a delegate constructor to handle payload variations much more elegantly than with multiple annotated constructors.最后,这可能是有意义的,例如,如果 JSON 是动态的,那么也许应该考虑使用委托构造函数来比使用多个带注释的构造函数更优雅地处理负载变化。

Also note that Jackson orders creators by priority , for example in this code :另请注意,杰克逊按优先级创作者进行排序,例如在此代码中:

// 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");

This time Jackson won't raise an error, but Jackson will only use the delegate constructor Phone(Map<String, Object> properties) , which means the Phone(@JsonProperty("value") String value) is never used.这一次 Jackson 不会引发错误,但 Jackson 将只使用委托构造函数Phone(Map<String, Object> properties) ,这意味着永远不会使用Phone(@JsonProperty("value") String value)

If I got right what you are trying to achieve, you can solve it without a constructor overload .如果我正确地完成了您要实现的目标,您可以在没有构造函数重载的情况下解决它。

If you just want to put null values in the attributes not present in a JSON or a Map you can do the following:如果您只想将空值放在 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;
    }
}

That was my case when I found your question.当我发现你的问题时,这就是我的情况。 It took me some time to figure out how to solve it, maybe that's what you were tring to do.我花了一些时间来弄清楚如何解决它,也许这就是你想要做的。 @Brice solution did not work for me. @Brice 解决方案对我不起作用。

If you don't mind doing a little more work, you can deserialize the entity manually:如果您不介意做更多的工作,您可以手动反序列化实体:

@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");
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM