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:

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 to do this, I get the following

Conflicting property-based creators: there were already ... {interface org.codehaus.jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator ()}] met ..., annotations: {interface org.codehaus. jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator ()}]

Is there a way to deserialize a class with overloaded constructors using Jackson?

thank

+45
java json jackson
Apr 10 '13 at 16:12
source share
2 answers

Although it is not properly documented, you can only have one creator for each type. You can have as many constructors as you want in your type, but only one of them should contain the @JsonCreator annotation.

+73
Apr 10 '13 at 16:32
source share

This is still saved for the Jackson 2.7.0 data file.

The Jackson @JsonCreator annotation 2.5 javadoc or Jackson annotation documentation (constructor s and factory method s ) suggest that several constructors can be noted.

Token annotations that can be used to define factory constructors and methods as one to use for instances of new instances of the associated class.

Looking at the code that identifies the creators, it seems that Jackson CreatorCollector ignores overloaded constructors because it only checks the first argument to 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 is the first identified creator of the constructor.
  • newOne is the creator of the overloaded constructor.

This means that such code will not 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 cannot be future proof .




The documentation is unclear as to how object creation works; from what I am compiling from the code, however, is that you can mix different methods:

For example, you can use the static factory method annotated with @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 perfect. In the end, it might make sense, for example. if json is dynamic, then it might be worth using the delegate constructor to handle payload changes much more elegantly than with a few annotated constructors.

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 will not throw an error, but Jackson will only use the Phone(Map<String, Object> properties) delegate constructor Phone(Map<String, Object> properties) , which means that Phone(@JsonProperty("value") String value) never used.

+28
Jul 01 '16 at 14:32
source share



All Articles