Mam problem z deserializacją łańcucha JSON za pomocą Gson. Otrzymuję szereg poleceń. Poleceniem może być start, stop lub inny typ polecenia. Oczywiście mam polimorfizm, a polecenie start / stop dziedziczy po poleceniu.
Jak mogę serializować go z powrotem do właściwego obiektu polecenia za pomocą gson?
Wydaje się, że otrzymuję tylko typ podstawowy, czyli zadeklarowany typ, a nigdy typ środowiska uruchomieniowego.
Odpowiedzi:
Trochę za późno, ale dzisiaj musiałem zrobić dokładnie to samo. Tak więc, opierając się na moich badaniach i używając gson-2.0, naprawdę nie chcesz używać metody registerTypeHierarchyAdapter , ale raczej bardziej przyziemnego registerTypeAdapter . I na pewno nie musisz robić instanceofs ani pisać adapterów dla klas pochodnych: wystarczy jeden adapter dla klasy bazowej lub interfejsu, oczywiście pod warunkiem, że jesteś zadowolony z domyślnej serializacji klas pochodnych. W każdym razie, oto kod (usunięto pakiet i import) (dostępny również na github ):
Klasa bazowa (interfejs w moim przypadku):
Dwie klasy pochodne, Cat:
I pies:
IAnimalAdapter:
I klasa Test:
Po uruchomieniu Test :: main otrzymasz następujące dane wyjściowe:
W rzeczywistości zrobiłem to powyżej przy użyciu metody registerTypeHierarchyAdapter , ale wydawało się, że wymagało to zaimplementowania niestandardowych klas serializatora / deserializatora DogAdapter i CatAdapter, które są trudne do utrzymania za każdym razem, gdy chcesz dodać kolejne pole do Dog lub Cat.
źródło
Gson ma obecnie mechanizm do rejestrowania adaptera hierarchii typów, który podobno można skonfigurować do prostej deserializacji polimorficznej, ale nie widzę, jak to się dzieje, ponieważ adapter hierarchii typów wydaje się być po prostu połączonym serializatorem / deserializatorem / twórcą instancji, pozostawiając szczegóły tworzenia instancji koderowi, bez podawania rzeczywistej rejestracji typu polimorficznego.
Wygląda na to, że Gson wkrótce będzie miał
RuntimeTypeAdapter
prostszą deserializację polimorficzną. Więcej informacji można znaleźć pod adresem http://code.google.com/p/google-gson/issues/detail?id=231 .Jeśli użycie nowego
RuntimeTypeAdapter
nie jest możliwe i musisz użyć Gson, myślę, że będziesz musiał stworzyć własne rozwiązanie, rejestrując niestandardowy deserializator jako adapter hierarchii typów lub jako adapter typów. Oto jeden z takich przykładów.źródło
RuntimeTypeAdapter
jest teraz ukończony, niestety nie wygląda jeszcze tak, jakby był w rdzeniu Gson. :-(Marcus Junius Brutus miał świetną odpowiedź (dzięki!). Aby rozszerzyć jego przykład, możesz uczynić jego klasę adaptera ogólną, aby działała dla wszystkich typów obiektów (nie tylko IAnimal) z następującymi zmianami:
A w klasie testowej:
źródło
GSON ma tutaj całkiem niezły przypadek testowy pokazujący, jak zdefiniować i zarejestrować adapter hierarchii typów.
http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739
Aby tego użyć, wykonaj następujące czynności:
Metoda serializacji karty może być kaskadowa, jeśli-else sprawdź, jakiego typu jest ona serializowana.
Deserializacja jest trochę hakerska. W przykładzie testu jednostkowego sprawdza istnienie atrybutów ostrzegawczych, aby zdecydować, do której klasy należy dokonać deserializacji. Jeśli możesz zmienić źródło obiektu, który serializujesz, możesz dodać atrybut „classType” do każdej instancji, która zawiera FQN nazwy klasy instancji. Jest to jednak tak bardzo nie-obiektowe.
źródło
Firma Google wydała własną RuntimeTypeAdapterFactory do obsługi polimorfizmu, ale niestety nie jest to część rdzenia gson (musisz skopiować i wkleić klasę do projektu).
Przykład:
Tutaj zamieściłem pełny działający przykład przy użyciu modeli Animal, Dog and Cat.
Myślę, że lepiej jest polegać na tym adapterze, niż wdrażać go od nowa.
źródło
Minęło dużo czasu, ale nie mogłem znaleźć naprawdę dobrego rozwiązania w Internecie. Oto mały zwrot w rozwiązaniu @ MarcusJuniusBrutus, który pozwala uniknąć nieskończonej rekurencji.
Zachowaj ten sam deserializator, ale usuń serializator -
Następnie w swojej oryginalnej klasie dodaj pole z
@SerializedName("CLASSNAME")
. Sztuczka polega teraz na zainicjowaniu tego w konstruktorze klasy bazowej , więc przekształć swój interfejs w klasę abstrakcyjną.Powodem, dla którego nie ma tutaj nieskończonej rekurencji, jest to, że przekazujemy aktualną klasę środowiska wykonawczego (tj. Dog not IAnimal) do
context.deserialize
. To nie wywoła naszego adaptera typu, o ile używamy,registerTypeAdapter
a nieregisterTypeHierarchyAdapter
źródło
Zaktualizowana odpowiedź - najlepsze części wszystkich innych odpowiedzi
Ja opisuję rozwiązań dla różnych przypadków użycia i będzie zajęcie się nieskończonej rekurencji problem jak dobrze
Przypadek 1: Jesteś w kontroli klas , czyli można dostać się do pisania własnych
Cat
,Dog
zajęcia, jak równieżIAnimal
interfejs. Możesz po prostu zastosować rozwiązanie podane przez @ marcus-junius-brutus (najwyżej oceniana odpowiedź)Nie będzie nieskończonej rekurencji, jeśli istnieje wspólny interfejs podstawowy jako
IAnimal
Ale co, jeśli nie chcę implementować takiego
IAnimal
lub innego interfejsu?Wtedy @ marcus-junius-brutus (najwyżej oceniona odpowiedź) spowoduje nieskończony błąd rekurencji. W takim przypadku możemy zrobić coś takiego jak poniżej.
Musielibyśmy utworzyć konstruktor kopiujący wewnątrz klasy bazowej i podklasę opakowującą w następujący sposób:
.
I serializator dla typu
Cat
:Dlaczego więc konstruktor kopiujący?
Cóż, po zdefiniowaniu konstruktora kopiującego, bez względu na to, jak bardzo zmieni się klasa bazowa, opakowanie będzie nadal pełniło tę samą rolę. Po drugie, gdybyśmy nie zdefiniowali konstruktora kopiującego i po prostu podklasowali klasę bazową, musielibyśmy „rozmawiać” w kategoriach klasy rozszerzonej, tj
CatWrapper
. , . Jest całkiem możliwe, że komponenty mówią w kategoriach klasy bazowej, a nie typu opakowania.Czy istnieje łatwa alternatywa?
Jasne, zostało wprowadzone przez Google - oto
RuntimeTypeAdapterFactory
implementacja:Tutaj musisz wprowadzić pole o nazwie „typ”
Animal
i wartość tego samego wnętrza,Dog
aby było „pies”,Cat
aby było „kotem”Pełny przykład: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html
Przypadek 2: Nie kontrolujesz zajęć . Dołączasz do firmy lub korzystasz z biblioteki, w której klasy są już zdefiniowane, a Twój menedżer nie chce, abyś w jakikolwiek sposób je zmieniał - Możesz podklasować swoje klasy i zaimplementować wspólny interfejs znaczników (który nie ma żadnych metod ), takie jak
AnimalInterface
.Dawny:
.
Więc używalibyśmy
CatWrapper
zamiastCat
,DogWrapper
zamiastDog
iAlternativeAnimalAdapter
zamiastIAnimalAdapter
Wykonujemy test:
Wynik:
źródło
Jeśli chcesz zarządzać TypeAdapter dla typu i innym dla jego podtypu, możesz użyć TypeAdapterFactory w następujący sposób:
Ta fabryka wyśle najdokładniejszy TypeAdapter
źródło
Jeśli połączysz odpowiedź Marcusa Juniusa Brutusa z edycją user2242263, możesz uniknąć konieczności określania dużej hierarchii klas w swoim adapterze, definiując adapter jako pracujący na typie interfejsu. Następnie możesz podać domyślne implementacje toJSON () i fromJSON () w swoim interfejsie (który zawiera tylko te dwie metody) i mieć każdą klasę potrzebną do serializacji zaimplementuj swój interfejs. Aby poradzić sobie z rzutowaniem, w podklasach można zapewnić statyczną metodę fromJSON (), która deserializuje i wykonuje odpowiednie rzutowanie z poziomu interfejsu użytkownika. To zadziałało znakomicie dla mnie (po prostu uważaj na serializację / deserializację klas, które zawierają hashmapy - dodaj to podczas tworzenia wystąpienia swojego gson builder:
GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();
Mam nadzieję, że pomoże to komuś zaoszczędzić trochę czasu i wysiłku!
źródło