web-dev-qa-db-fra.com

Désérialiser JSON avec Jackson en types polymorphes - Un exemple complet me donne une erreur de compilation

J'essaie de suivre un didacticiel du programmeur Bruce, censé permettre la désérialisation de JSON polymorphe. 

La liste complète peut être trouvée ici Tutoriels pour programmeur Bruce (Great stuff btw)

J'ai travaillé sur les cinq premiers sans aucun problème, mais le dernier (Exemple 6) a posé un problème. Il s'agit bien sûr de celui dont j'ai vraiment besoin pour travailler.

Je reçois l'erreur suivante au moment de la compilation

La méthode readValue (JsonParser, Class) du type ObjectMapper n'est pas applicable pour les arguments (ObjectNode, Class)

et cela est causé par le bloc de code

  public Animal deserialize(  
      JsonParser jp, DeserializationContext ctxt)   
      throws IOException, JsonProcessingException  
  {  
    ObjectMapper mapper = (ObjectMapper) jp.getCodec();  
    ObjectNode root = (ObjectNode) mapper.readTree(jp);  
    Class<? extends Animal> animalClass = null;  
    Iterator<Entry<String, JsonNode>> elementsIterator =   
        root.getFields();  
    while (elementsIterator.hasNext())  
    {  
      Entry<String, JsonNode> element=elementsIterator.next();  
      String name = element.getKey();  
      if (registry.containsKey(name))  
      {  
        animalClass = registry.get(name);  
        break;  
      }  
    }  
    if (animalClass == null) return null;  
    return mapper.readValue(root, animalClass);
  }  
} 

Plus précisément par la ligne 

return mapper.readValue (root, animalClass);

Est-ce que quelqu'un a déjà rencontré ce problème auparavant et si oui, y avait-il une solution?

J'apprécierais toute aide que quiconque puisse donner Merci d'avance Jon D.

47
Jon Driscoll

Comme promis, je donne un exemple d'utilisation des annotations pour sérialiser/désérialiser des objets polymorphes. J'ai basé cet exemple dans la classe Animal du tutoriel que vous lisiez.

Tout d’abord votre classe Animal avec les annotations Json pour les sous-classes.

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "Dog"),

    @JsonSubTypes.Type(value = Cat.class, name = "Cat") }
)
public abstract class Animal {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Ensuite, vos sous-classes, Dog et Cat.

public class Dog extends Animal {

    private String breed;

    public Dog() {

    }

    public Dog(String name, String breed) {
        setName(name);
        setBreed(breed);
    }

    public String getBreed() {
        return breed;
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }
}

public class Cat extends Animal {

    public String getFavoriteToy() {
        return favoriteToy;
    }

    public Cat() {}

    public Cat(String name, String favoriteToy) {
        setName(name);
        setFavoriteToy(favoriteToy);
    }

    public void setFavoriteToy(String favoriteToy) {
        this.favoriteToy = favoriteToy;
    }

    private String favoriteToy;

}

Comme vous pouvez le constater, il n'y a rien de spécial pour Cat et Dog, le seul qui en ait connaissance est la abstract class Animal. Ainsi, lors de la désérialisation, vous ciblerez Animal et ObjectMapper renverra l'instance réelle comme vous pouvez le voir dans le test suivant:

public class Test {

    public static void main(String[] args) {

        ObjectMapper objectMapper = new ObjectMapper();

        Animal myDog = new Dog("ruffus","english shepherd");

        Animal myCat = new Cat("goya", "mice");

        try {
            String dogJson = objectMapper.writeValueAsString(myDog);

            System.out.println(dogJson);

            Animal deserializedDog = objectMapper.readValue(dogJson, Animal.class);

            System.out.println("Deserialized dogJson Class: " + deserializedDog.getClass().getSimpleName());

            String catJson = objectMapper.writeValueAsString(myCat);

            Animal deseriliazedCat = objectMapper.readValue(catJson, Animal.class);

            System.out.println("Deserialized catJson Class: " + deseriliazedCat.getClass().getSimpleName());



        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

Sortie après l'exécution de la classe Test:

{"@type":"Dog","name":"ruffus","breed":"english shepherd"}

Deserialized dogJson Class: Dog

{"@type":"Cat","name":"goya","favoriteToy":"mice"}

Deserialized catJson Class: Cat

J'espère que cela t'aides,

Jose Luis

90
jbarrueta

Un moyen simple d'activer la sérialisation/désérialisation polymorphe via la bibliothèque Jackson consiste à configurer globalement le mappeur d'objet de Jackson (jackson.databind.ObjectMapper) afin qu'il ajoute des informations, telles que le type de classe concret, telles que les classes abstraites. 

Pour ce faire, assurez-vous que votre mappeur est configuré correctement. Par exemple:

Option 1: prise en charge de la sérialisation/désérialisation polymorphe des classes abstraites (et des classes typées Object)

jacksonObjectMapper.enableDefaultTyping(
    ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE); 

Option 2: Prend en charge la sérialisation/désérialisation polymorphe des classes abstraites (et des classes typées Object) et des tableaux de ces types.

jacksonObjectMapper.enableDefaultTyping(
    ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS); 

Référence: https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

1
AmitW

Une seule ligne avant la déclaration de la classe Animal pour une sérialisation/désérialisation polymorphe correcte:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class Animal {
   ...
}

Cette ligne signifie: ajouter une méta-propriété sur la sérialisation ou lire une méta-propriété sur la désérialisation (include = JsonTypeInfo.As.PROPERTY) appelée "@class" (property = "@class") contenant le nom complet Java. _ nom de classe (use = JsonTypeInfo.Id.CLASS).

Ainsi, si vous créez un JSON directement (sans sérialisation), n'oubliez pas d'ajouter la méta-propriété "@class" avec le nom de classe souhaité pour une désérialisation correcte.

Plus d'informations ici

0
Marco