Ich habe ein Problem beim Deserialisieren eines JSON-Strings mit Gson. Ich erhalte eine Reihe von Befehlen. Der Befehl kann Start, Stopp oder eine andere Art von Befehl sein. Natürlich habe ich Polymorphismus und Start / Stopp-Befehl erbt von Befehl.
Wie kann ich es mit gson zurück zum richtigen Befehlsobjekt serialisieren?
Scheint, dass ich nur den Basistyp bekomme, das ist der deklarierte Typ und niemals der Laufzeittyp.
java
json
polymorphism
gson
deserialization
Sophie
quelle
quelle
Antworten:
Das ist etwas spät, aber ich musste heute genau das Gleiche tun. Basierend auf meinen Recherchen und wenn Sie gson-2.0 verwenden, möchten Sie wirklich nicht die registerTypeHierarchyAdapter- Methode verwenden, sondern den profaneren registerTypeAdapter . Und Sie müssen sicherlich keine Instanzen von Instanzen erstellen oder Adapter für die abgeleiteten Klassen schreiben: nur einen Adapter für die Basisklasse oder Schnittstelle, vorausgesetzt natürlich, Sie sind mit der Standardserialisierung der abgeleiteten Klassen zufrieden. Wie auch immer, hier ist der Code (Paket und Importe entfernt) (auch in Github verfügbar ):
Die Basisklasse (Schnittstelle in meinem Fall):
Die zwei abgeleiteten Klassen, Cat:
Und Hund:
Der IAnimalAdapter:
Und die Testklasse:
Wenn Sie Test :: main ausführen, erhalten Sie die folgende Ausgabe:
Ich habe das oben Genannte tatsächlich auch mit der registerTypeHierarchyAdapter- Methode durchgeführt, aber dafür mussten anscheinend benutzerdefinierte DogAdapter- und CatAdapter-Serializer- / Deserializer-Klassen implementiert werden, die jedes Mal, wenn Sie Dog oder Cat ein weiteres Feld hinzufügen möchten, schwierig zu pflegen sind.
quelle
Gson verfügt derzeit über einen Mechanismus zum Registrieren eines Typhierarchie- Adapters , der Berichten zufolge für eine einfache polymorphe Deserialisierung konfiguriert werden kann. Ich sehe jedoch nicht, wie dies der Fall ist, da ein Typhierarchie-Adapter nur ein kombinierter Serializer / Deserializer / Instanz-Ersteller zu sein scheint. Überlassen Sie die Details der Instanzerstellung dem Codierer, ohne eine tatsächliche polymorphe Typregistrierung bereitzustellen.
Es sieht so aus, als ob Gson bald die
RuntimeTypeAdapter
für eine einfachere polymorphe Deserialisierung haben wird. Weitere Informationen finden Sie unter http://code.google.com/p/google-gson/issues/detail?id=231 .Wenn die Verwendung des neuen
RuntimeTypeAdapter
nicht möglich ist und Sie Gson verwenden müssen, müssen Sie wahrscheinlich Ihre eigene Lösung entwickeln und einen benutzerdefinierten Deserializer entweder als Typhierarchie-Adapter oder als Typ-Adapter registrieren. Das Folgende ist ein solches Beispiel.quelle
RuntimeTypeAdapter
ist jetzt fertig, leider sieht es noch nicht so aus, als wäre es im Gson-Kern. :-(Marcus Junius Brutus hatte eine großartige Antwort (danke!). Um sein Beispiel zu erweitern, können Sie seine Adapterklasse generisch so gestalten, dass sie für alle Objekttypen (nicht nur für IAnimal) mit den folgenden Änderungen funktioniert:
Und in der Testklasse:
quelle
GSON hat hier einen ziemlich guten Testfall, der zeigt, wie ein Typhierarchie-Adapter definiert und registriert wird.
http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739
Um dies zu verwenden, gehen Sie wie folgt vor:
Die Serialisierungsmethode des Adapters kann eine kaskadierende if-else-Überprüfung des Serialisierungstyps sein.
Deserialisieren ist ein bisschen hacky. Im Unit-Test-Beispiel wird geprüft, ob verräterische Attribute vorhanden sind, um zu entscheiden, für welche Klasse dererialisiert werden soll. Wenn Sie die Quelle des zu serialisierenden Objekts ändern können, können Sie jeder Instanz ein Attribut 'classType' hinzufügen, das den FQN des Namens der Instanzklasse enthält. Dies ist jedoch sehr unobjektorientiert.
quelle
Google hat eine eigene RuntimeTypeAdapterFactory veröffentlicht , um den Polymorphismus zu behandeln. Leider ist sie nicht Teil des Gson-Kerns (Sie müssen die Klasse kopieren und in Ihr Projekt einfügen).
Beispiel:
Hier habe ich ein voll funktionsfähiges Beispiel mit den Modellen Animal, Dog und Cat veröffentlicht.
Ich denke, es ist besser, sich auf diesen Adapter zu verlassen, als ihn von Grund auf neu zu implementieren.
quelle
Lange Zeit ist vergangen, aber ich konnte online keine wirklich gute Lösung finden. Hier ist eine kleine Wendung der Lösung von @ MarcusJuniusBrutus, die die unendliche Rekursion vermeidet.
Behalten Sie den gleichen Deserializer bei, entfernen Sie jedoch den Serializer -
Fügen Sie dann in Ihrer ursprünglichen Klasse ein Feld mit hinzu
@SerializedName("CLASSNAME")
. Der Trick besteht nun darin, dies im Konstruktor der Basisklasse zu initialisieren. Machen Sie Ihre Schnittstelle also zu einer abstrakten Klasse.Der Grund, warum es hier keine unendliche Rekursion gibt, ist, dass wir die eigentliche Laufzeitklasse (dh Dog not IAnimal) an übergeben
context.deserialize
. Dies wird unseren Typadapter nicht aufrufen, solange wir ihn verwendenregisterTypeAdapter
und nichtregisterTypeHierarchyAdapter
quelle
Aktualisierte Antwort - Beste Teile aller anderen Antworten
Ich beschreibe Lösungen für verschiedene Anwendungsfälle und würde auch das Problem der unendlichen Rekursion ansprechen
Fall 1: Sie sind in der Steuerung der Klassen , dh Sie erhalten eine eigene zu schreiben
Cat
,Dog
Klassen sowie dieIAnimal
Schnittstelle. Sie können einfach der Lösung von @ marcus-junius-brutus folgen (die am besten bewertete Antwort).Es wird keine unendliche Rekursion geben, wenn es eine gemeinsame Basisschnittstelle als gibt
IAnimal
Aber was ist, wenn ich die
IAnimal
oder eine solche Schnittstelle nicht implementieren möchte ?Dann erzeugt @ marcus-junius-brutus (die am besten bewertete Antwort) einen unendlichen Rekursionsfehler. In diesem Fall können wir etwas wie das Folgende tun.
Wir müssten einen Kopierkonstruktor innerhalb der Basisklasse und eine Wrapper-Unterklasse wie folgt erstellen :
.
Und der Serializer für den Typ
Cat
:Warum also ein Kopierkonstruktor?
Sobald Sie den Kopierkonstruktor definiert haben, wird Ihr Wrapper unabhängig von der Änderung der Basisklasse dieselbe Rolle übernehmen. Zweitens, wenn wir keinen Kopierkonstruktor definieren und einfach die Basisklasse unterordnen, müssten wir in Bezug auf die erweiterte Klasse "sprechen", d
CatWrapper
. H. Es ist durchaus möglich, dass Ihre Komponenten in Bezug auf die Basisklasse und nicht auf den Wrapper-Typ sprechen.Gibt es eine einfache Alternative?
Sicher, es wurde jetzt von Google eingeführt - dies ist die
RuntimeTypeAdapterFactory
Implementierung:Hier müssten Sie ein Feld namens "Typ" einfügen
Animal
und den Wert desselben im InnerenDog
als "Hund",Cat
als "Katze" angeben.Vollständiges Beispiel: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html
Fall 2: Sie haben keine Kontrolle über die Klassen . Sie treten einer Firma bei oder verwenden eine Bibliothek, in der die Klassen bereits definiert sind und Ihr Manager nicht möchte, dass Sie sie in irgendeiner Weise ändern. Sie können Ihre Klassen in Unterklassen unterteilen und sie eine gemeinsame Markierungsschnittstelle implementieren lassen (die keine Methoden hat ) wie
AnimalInterface
.Ex:
.
Also würden wir
CatWrapper
anstelle vonCat
,DogWrapper
anstelle vonDog
undAlternativeAnimalAdapter
anstelle von verwendenIAnimalAdapter
Wir führen einen Test durch:
Ausgabe:
quelle
Wenn Sie einen TypeAdapter für einen Typ und einen anderen für seinen Untertyp verwalten möchten, können Sie eine TypeAdapterFactory wie folgt verwenden:
Diese Fabrik sendet den genauesten TypeAdapter
quelle
Wenn Sie die Antwort von Marcus Junius Brutus mit der Bearbeitung von user2242263 kombinieren, können Sie vermeiden, dass Sie in Ihrem Adapter eine große Klassenhierarchie angeben müssen, indem Sie Ihren Adapter so definieren, dass er an einem Schnittstellentyp arbeitet. Sie können dann Standardimplementierungen von toJSON () und fromJSON () in Ihrer Schnittstelle bereitstellen (die nur diese beiden Methoden enthält) und jede Klasse, die Sie zur Serialisierung benötigen, Ihre Schnittstelle implementieren lassen. Um das Casting zu behandeln, können Sie in Ihren Unterklassen eine statische fromJSON () -Methode bereitstellen, die das entsprechende Casting von Ihrem Schnittstellentyp deserialisiert und ausführt. Dies hat bei mir hervorragend funktioniert (seien Sie vorsichtig beim Serialisieren / Deserialisieren von Klassen, die Hashmaps enthalten - fügen Sie dies hinzu, wenn Sie Ihren gson-Builder instanziieren:
GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();
Hoffe, das hilft jemandem, Zeit und Mühe zu sparen!
quelle