96 votes

Comment désérialiser une classe avec des constructeurs surchargés en utilisant JsonCreator ?

J'essaie de désérialiser une instance de cette classe en utilisant 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
}
}

Lorsque j'essaie de le faire, j'obtiens le résultat suivant

Conflit entre les créateurs basés sur les propriétés : il y avait déjà ... {interface org.codehaus.jackson.annotate.JsonCreator @org.codehaus.jackson.annotate.JsonCreator()}], a rencontré ... , annotations : {interface org.codehaus.jackson.annotate.JsonCreator @org.codehaus.jackson.annotate.JsonCreator()}]]

Existe-t-il un moyen de désérialiser une classe avec des constructeurs surchargés en utilisant Jackson ?

Gracias

129voto

Perception Points 42290

Bien que ce ne soit pas correctement documenté, vous ne pouvez avoir qu'un seul créateur par type. Vous pouvez avoir autant de constructeurs que vous le souhaitez dans votre type, mais un seul d'entre eux doit avoir une icône @JsonCreator sur celui-ci.

81voto

Brice Points 5129

EDITAR : Voici, dans un article de blog par les mainteneurs de Jackson, il semble que la 2.12 pourrait voir des améliorations en ce qui concerne l'injection de constructeur. (La version actuelle au moment de cette édition est 2.11.1)

Amélioration de l'autodétection des créateurs de constructeurs, y compris la résolution et l'atténuation des problèmes liés aux constructeurs ambigus à un argument (délégation et propriétés).


Cela reste vrai pour Jackson databind 2.7.0.

Le Jackson @JsonCreator annotation 2.5 javadoc o Documentation sur les annotations Jackson grammaire ( Constructeur s et méthode d'usine s ) laissez croire en effet que l'on peut marque plusieurs constructeurs.

Annotation de marqueur qui peut être utilisée pour définir les constructeurs et les méthodes de fabrique comme étant ceux à utiliser pour instancier de nouvelles instances de la classe associée.

En regardant le code où le créateurs sont identifiés, il semble que le Jackson CreatorCollector ignore Constructeurs surchargés parce que c'est seulement vérifie le premier argument du constructeur .

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 est le premier créateur de constructeur identifié.
  • newOne est le créateur du constructeur surchargé.

Cela signifie que le code comme celui-ci ne fonctionne pas

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

Mais ce code fonctionnera :

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

C'est un peu bricolé et peut ne pas être à l'épreuve du temps. .


La documentation est vague sur la façon dont la création d'objets fonctionne ; d'après ce que j'ai compris du code, il est possible de mélanger différentes méthodes :

Par exemple, on peut avoir une méthode d'usine statique annotée avec @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");

Cela fonctionne mais ce n'est pas idéal. En fin de compte, cela pourrait avoir du sens, par exemple si le JSON est le suivant dynamique alors il faudrait peut-être envisager d'utiliser un constructeur délégué pour gérer les variations de charge utile de manière beaucoup plus élégante qu'avec de multiples constructeurs annotés.

Notez également que Jackson ordres créateurs par priorité par exemple dans ce 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");

Cette fois-ci, Jackson ne soulèvera pas d'erreur, mais il n'utilisera que l'option délégué Constructeur Phone(Map<String, Object> properties) ce qui signifie que le Phone(@JsonProperty("value") String value) n'est jamais utilisé.

8voto

Si j'ai bien compris ce que vous essayez de faire, vous pouvez le résoudre. sans surcharge du constructeur .

Si vous voulez simplement mettre des valeurs nulles dans les attributs non présents dans un JSON ou une Map, vous pouvez faire ce qui suit :

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

C'était mon cas lorsque j'ai trouvé votre question. J'ai mis du temps à trouver la solution, c'est peut-être ce que vous cherchez à faire. Solution @Brice n'a pas fonctionné pour moi.

3voto

Crummy Points 394

Si cela ne vous dérange pas de faire un peu plus de travail, vous pouvez désérialiser l'entité manuellement :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X