JSON mit Jackson in polymorphe Typen deserialisieren - Ein vollständiges Beispiel gibt mir einen Kompilierungsfehler

77

Ich versuche, ein Tutorial von Programmierer Bruce durchzuarbeiten, das die Deserialisierung von polymorphem JSON ermöglichen soll.

Die vollständige Liste finden Sie hier Programmer Bruce Tutorials (Great stuff btw)

Ich habe die ersten fünf ohne Probleme durchgearbeitet, aber ich habe beim letzten einen Haken bekommen (Beispiel 6), was natürlich derjenige ist, den ich wirklich brauche, um arbeiten zu können.

Beim Kompilieren wird der folgende Fehler angezeigt

Die Methode readValue (JsonParser, Class) vom Typ ObjectMapper gilt nicht für die Argumente (ObjectNode, Class).

und es wird durch den Codeblock verursacht

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

Speziell durch die Linie

return mapper.readValue (root, animalClass);

Hat jemand schon einmal darauf gestoßen und wenn ja, gab es eine Lösung?

Ich würde mich über jede Hilfe freuen, die jemand im Voraus geben kann. Jon D.

Jon Driscoll
quelle
Welche Jackson-Version verwenden Sie, das Tutorial geht von Jackson 1.x aus, auch aus irgendeinem Grund, warum nicht eine auf Anmerkungen basierende Deserialisierung für polymorphe Instanzen bevorzugen?
Jbarrueta
Ich benutze 2.5. Ich kann sehen, ob ein Downgrade auf 1.X das Problem lösen wird. Können Sie auch ein Tutorial / Beispiel empfehlen, das die Verwendung von Anmerkungen zur Behebung dieses Problems zeigt?
Jon Driscoll
Ja, ich würde Ihnen nicht empfehlen, ein Downgrade durchzuführen. Ich werde Ihnen gerne ein Beispiel geben.
Jbarrueta
2
Hier ist ein weiterer Artikel, der die verschiedenen Möglichkeiten zur Durchführung einer polymorphen Serialisierung / Deserialisierung ausführlich erklärt
Jerome L
Ich habe gerade eine (wohl) einfachere Lösung hinzugefügt, die das Deserialisieren auf verschiedene Typen basierend auf dem Vorhandensein einer Eigenschaft handhabt
bernie

Antworten:

133

Wie versprochen, gebe ich ein Beispiel für die Verwendung von Anmerkungen zum Serialisieren / Deserialisieren polymorpher Objekte. Dieses Beispiel habe ich in der AnimalKlasse aus dem Tutorial, das Sie gelesen haben, verwendet.

Zunächst Ihre AnimalKlasse mit den Json-Anmerkungen für die Unterklassen.

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

}

Dann deine Unterklassen Dogund 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;

}

Wie Sie sehen können, gibt es nichts Besonderes für Catund Dogdie einzige, die über sie wissen das ist abstractKlasse Animal, also wenn Deserialisieren, werden Ziel , das Sie Animalund das ObjectMapperwird die aktuelle Instanz zurück , wie Sie im folgenden Test sehen können:

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();
        }

    }
}

Ausgabe nach dem Ausführen der TestKlasse:

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

Deserialized dogJson Class: Dog

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

Deserialized catJson Class: Cat

Hoffe das hilft,

Jose Luis

jbarrueta
quelle
7
Was ist, wenn ich das Objekt von Retrofit ohne die @ type-Informationen erhalte?
Renan Bandeira
7
Ist dies ohne Verwendung der Annotation @JsonSubTypes möglich? Damit Unterklassen in anderen Paketen hinzugefügt werden können, nachdem die ursprüngliche Klasse erstellt wurde?
HDave
1
@KamalJoshi Ja, Sie können dasselbe tun, wenn Animal eine Schnittstelle ist. Gerade getestet, indem das Tier in eine Schnittstelle geändert wurde und die gleiche Hauptmethode funktioniert hat. Bei Ihrer zweiten Frage ist es richtig, dass der Name nicht mit dem Dogin der Anmerkung verknüpften Namen zusammenhängt, sondern namenur ein Feld der serialisierten Klasse ist, wahrscheinlich um Verwirrung zu vermeiden, die ich in petNameder AnimalKlasse anstelle von hätte verwenden können name. Das Jackson-Anmerkungsfeld nameist der Wert, der auf den serialisierten Json als Bezeichner festgelegt werden soll. HTH.
Jbarrueta
4
@ HDave Ich habe einen Kern erstellt, der @JsonTypeIdResolver(wahrscheinlich) das tut, was Sie brauchen: gist.github.com/root-talis/36355f227ff5bb7a057ff7ad842d37a3
G.
1
@ user1300959 nein, sie müssen nicht implementiert werden Serializable, damit dieses Beispiel funktioniert.
Jbarrueta
9

Sie benötigen nur eine Zeile vor der Deklaration der Klasse Animalfür die korrekte polymorphe Serialisierung / Deserialisierung:

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

Diese Zeile bedeutet: include = JsonTypeInfo.As.PROPERTYFügen Sie bei der Serialisierung eine Meta-Eigenschaft hinzu oder lesen Sie bei der Deserialisierung ( ) eine Meta-Eigenschaft namens "@class" ( property = "@class"), die den vollständig qualifizierten Java-Klassennamen ( use = JsonTypeInfo.Id.CLASS) enthält.

Wenn Sie also einen JSON direkt (ohne Serialisierung) erstellen, denken Sie daran, die Meta-Eigenschaft "@class" mit dem gewünschten Klassennamen für die korrekte Deserialisierung hinzuzufügen.

Weitere Informationen hier

Marco
quelle
2

Eine einfache Möglichkeit, die polymorphe Serialisierung / Deserialisierung über die Jackson-Bibliothek zu aktivieren, besteht darin, den Jackson-Objekt-Mapper (jackson.databind.ObjectMapper) global zu konfigurieren, um Informationen wie den konkreten Klassentyp für bestimmte Arten von Klassen, z. B. abstrakte Klassen, hinzuzufügen.

Stellen Sie dazu einfach sicher, dass Ihr Mapper richtig konfiguriert ist. Zum Beispiel:

Option 1: Unterstützung der polymorphen Serialisierung / Deserialisierung für abstrakte Klassen (und objekttypisierte Klassen)

jacksonObjectMapper.enableDefaultTyping(
    ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE); 

Option 2: Unterstützung der polymorphen Serialisierung / Deserialisierung für abstrakte Klassen (und objekttypisierte Klassen) und Arrays dieser Typen.

jacksonObjectMapper.enableDefaultTyping(
    ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS); 

Referenz: https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

AmitW
quelle
1
Wo soll ich das hinstellen (ich benutze den Ruhezustand)?
AndroidTank
In einem Kontext, in dem der ObjectMapper definiert ist und bevor er verwendet wird. Wenn Sie beispielsweise einen statischen ObjectMapper verwenden, können Sie einen statischen Absatz hinzufügen und diese Werte dort festlegen.
AmitW
1
Ich benutze keinen Objectmapper. Es funktioniert einfach ohne Definition.
AndroidTank
Der von Ihnen angegebene Link scheint defekt zu sein ... könnte es sein, dass die URL jetzt github.com/FasterXML/jackson-docs/wiki/… lautet ?
SJuan76
Es scheint, dass der von SJuan76 bereitgestellte Link jetzt funktioniert, der von Ihnen bereitgestellte Link enthält jedoch dieselben Informationen.
AmitW
-1

Wenn Sie dann die schnellere XML verwenden,

Diese Änderungen sind möglicherweise erforderlich

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;

in der Hauptmethode--

verwenden

SimpleModule module =
  new SimpleModule("PolymorphicAnimalDeserializerModule");

anstatt

new SimpleModule("PolymorphicAnimalDeserializerModule",
      new Version(1, 0, 0, null));

Nehmen Sie in der Funktion Animal deserialize () die folgenden Änderungen vor

//Iterator<Entry<String, JsonNode>> elementsIterator =  root.getFields();
Iterator<Entry<String, JsonNode>> elementsIterator = root.fields();

//return mapper.readValue(root, animalClass);
return  mapper.convertValue(root, animalClass); 

Dies funktioniert für schnellerxml.jackson. Wenn es sich immer noch über die Klassenfelder beschwert. Verwenden Sie für die Feldnamen das gleiche Format wie im json (mit "_" -underscore). da dies
//mapper.setPropertyNamingStrategy(new CamelCaseNamingStrategy()); möglicherweise nicht unterstützt wird.

abstract class Animal
{
  public String name;
}

class Dog extends Animal
{
  public String breed;
  public String leash_color;
}

class Cat extends Animal
{
  public String favorite_toy;
}

class Bird extends Animal
{
  public String wing_span;
  public String preferred_food;
}
ravi.zombie
quelle