2010-09-02 14 views
32

Mam obiekt drzewa w formacie JSON Próbuję deserializować z Gsonem. Każdy węzeł zawiera węzły podrzędne jako pola typu obiektu Węzeł. Węzeł to interfejs, który ma kilka implementacji klas konkretnych. Czy podczas procesu deserializacji mogę komunikować się z Gsonem, którą klasę konkretną implementować podczas deserializacji węzła, jeśli nie wiem z góry, do którego typu należy ten węzeł? Każdy węzeł ma pole członkowskie określające typ. Czy istnieje sposób na uzyskanie dostępu do pola, gdy obiekt jest w postaci szeregowej i jakoś przekazać typ do Gson?Deserializowanie klasy abstrakcyjnej w Gson

Dzięki!

+0

Spójrz na [http://stackoverflow.com/a/22081826/3315914](http://stackoverflow.com/a/22081826/3315914) – rpax

Odpowiedz

40

sugeruję dodanie niestandardowych JsonDeserializer dla Node s:

Gson gson = new GsonBuilder() 
    .registerTypeAdapter(Node.class, new NodeDeserializer()) 
    .create(); 

Będziesz mógł uzyskać dostęp do JsonElement reprezentujący węzeł w metodzie Deserializator użytkownika, przekonwertować do JsonObject i odzyskać pole, które określa typ. Na tej podstawie można utworzyć instancję odpowiedniego typu Node.

+0

który pracował idealnie. Dzięki! – user437480

+0

@ eng-carlin Jest zwyczaj przyjmowania i akceptowania odpowiedzi, jeśli to działa, więc jeśli mógłbyś to zrobić, byłbym wdzięczny. – ColinD

+0

Kliknij zielony znacznik wyboru. Pobiłbym ciebie, ale moja reputacja nie jest wystarczająco wysoka :( – user437480

22

Będziesz musiał zarejestrować zarówno JSONSerializer, jak i JSONDeserializer. Ponadto można zaimplementować rodzajowe adapter dla wszystkich interfejsów w następujący sposób:

  • Podczas serializacji: Dodaj meta-informacje rzeczywistego typu klasy impl.
  • Podczas deserializacji: Zapomniałem, że meta informacji i wywołać JSONDeserailize tej klasy

Oto implementacja że Użyłem do siebie i działa bez zarzutu.

public class PropertyBasedInterfaceMarshal implements 
     JsonSerializer<Object>, JsonDeserializer<Object> { 

    private static final String CLASS_META_KEY = "CLASS_META_KEY"; 

    @Override 
    public Object deserialize(JsonElement jsonElement, Type type, 
      JsonDeserializationContext jsonDeserializationContext) 
      throws JsonParseException { 
     JsonObject jsonObj = jsonElement.getAsJsonObject(); 
     String className = jsonObj.get(CLASS_META_KEY).getAsString(); 
     try { 
      Class<?> clz = Class.forName(className); 
      return jsonDeserializationContext.deserialize(jsonElement, clz); 
     } catch (ClassNotFoundException e) { 
      throw new JsonParseException(e); 
     } 
    } 

    @Override 
    public JsonElement serialize(Object object, Type type, 
      JsonSerializationContext jsonSerializationContext) { 
     JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass()); 
     jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY, 
       object.getClass().getCanonicalName()); 
     return jsonEle; 
    } 

} 

Następnie można zarejestrować ten adapter dla wszystkich interfejsów następująco

Gson gson = new GsonBuilder() 
     .registerTypeAdapter(IInterfaceOne.class, 
       new PropertyBasedInterfaceMarshal()) 
     .registerTypeAdapter(IInterfaceTwo.class, 
       new PropertyBasedInterfaceMarshal()).create(); 
+0

co masz na myśli przez IInterfaceOne.class i IInterfaceTwo.class –

+0

Dziękuję za zapewnienie najbardziej eleganckie/wielokrotnego użytku rozwiązanie tego problemu. Używam '_class' jako' CLASS_META_KEY', tak jak robi mongodb. – bekce

1

O ile mogę powiedzieć, to nie działa dla typów niepobieranie, lub dokładniej, sytuacje, w których typ konkretny służy do serializacji, a typ interfejsu służy do deserializacji. Oznacza to, że jeśli masz prostą klasę implementującą interfejs i serializujesz konkretną klasę, to określ interfejs do deserializacji, skończysz w sytuacji nie do odzyskania.

W powyższym przykładzie adapter typu jest zarejestrowany w interfejsie, ale gdy serializuje się za pomocą klasy konkretnej, nie zostanie użyty, co oznacza, że ​​dane CLASS_META_KEY nigdy nie zostaną ustawione.

Jeśli określisz adapter jako adapter hierarchiczny (dzięki czemu program gson użyje go do wszystkich typów w hierarchii), skończysz w nieskończonej pętli, ponieważ serializator będzie nadal wywoływał samą siebie.

Ktoś wie, jak serializować z konkretnej implementacji interfejsu, a następnie deserializować, używając tylko interfejsu i instancji?

Domyślnie wydaje się, że gson utworzy konkretną instancję, ale nie ustawi jej pól.

Problem jest rejestrowane tutaj:

http://code.google.com/p/google-gson/issues/detail?id=411&q=interface

0

Musisz użyć TypeToken klasę z Google Gson. Trzeba będzie oczywiście ma rodzajowe klasy T, aby to działa

Type fooType = new TypeToken<Foo<Bar>>() {}.getType(); 

gson.toJson (foo, fooType);

gson.fromJson(json, fooType);