2016-02-09 17 views
13

Ich habe in etwa der folgenden StrukturInstanziieren Sie das untergeordnete Objekt mit den Parametern des übergeordneten Objekts, das mit GSON deserialisiert wird, und verwenden Sie Generics?

class MyDeserialParent<T extends MyChildInterface> { 

    MyChildInterface mSerialChild; 
    ... //some other fields (not 'type') 

} 

Aber es ist von einer unordentlichen JSON Struktur deserialisiert die beiden Eigenschaften des Kindes auf dem übergeordneten Knoten zurückgegeben werden, wie folgt.

{ 
    "myDeserialParents" : [ 
     { 
      ... //some parent properties 
      "type": "value", //used in a TypeAdapter to choose child implementation 
      "childProp1": "1", 
      "childProp2": "2", 
     }, 
     ... //more in this list 
    ] 
} 

Offensichtlich verhindert dies mich von nur mSerialChild Anmerkungen versehen mit SerializedName und lassen ein TypeAdapter seine Magie arbeiten. Also was ich hoffe zu tun ist, wenn MyDeserialParent deserialised ist, benutze "type", um die korrekte Betonklasse von MyChildInterface zu finden und eine neue mit childProp1 und childProp2 als Parameter für den Konstruktor zu erstellen. Ich weiß nicht, wie ich das machen soll.

kann ich mich vorstellen, einen TypeAdapter (JsonDeserializer) für MyDeserialParent mit und in deserialize das Typ-Feld erhalten (wie auch die beiden Kinder Eigenschaften), dann instanziiert den richtigen Beton für MyChildInterface mich.

Das bedeutet dann, ich muss meine MyDeserialParent Klasse (mit context.deserialize(json, MyDeserialParent.class)) erstellen und einen Setter mit MyChildInterface Instanz aufrufen. Das fühlt sich falsch an, als würde mir etwas fehlen. Gibt es einen besseren Weg?

Gibt es auch eine Möglichkeit, die Generics anzugeben (T auf MyDeserialParent), wenn ich manuell auch das übergeordnete Objekt erstellen? Oder bedeutet Type Erasure, dass dies nicht möglich ist? (Diese Frage ist weniger wichtig becase ich weiß, ich kann Typ Sicherheit bekommen, wenn ich bestimmte Subtypen von MyDeserialParent, die bereits auf T ableiten, aber ich möchte es vermeiden)

Antwort

3

Sie brauchen offensichtlich eine benutzerdefinierte TypeAdapter. Aber die heikelen Teile sind:

  • Ihre Eltern-Klasse ist ein Ober einer
  • mSerialChild ist nicht vom Typ T, aber vom Typ MyChildInterface
  • wollen wir das Parsen von Hand die json für jedes Kind Klasse vermeiden und in der Lage sein, dem übergeordneten Objekt Eigenschaften hinzuzufügen, ohne den gesamten Code ändern zu müssen.

In diesem Sinne, endete ich mit der folgenden Lösung.

public class MyParentAdapter implements JsonDeserializer<MyDeserialParent>{ 

    private static Gson gson = new GsonBuilder().create(); 
    // here is the trick: keep a map between "type" and the typetoken of the actual child class 
    private static final Map<String, Type> CHILDREN_TO_TYPETOKEN; 

    static{ 
     // initialize the mapping once 
     CHILDREN_TO_TYPETOKEN = new TreeMap<>(); 
     CHILDREN_TO_TYPETOKEN.put("value", new TypeToken<MyChild1>(){}.getType()); 
    } 


    @Override 
    public MyDeserialParent deserialize(JsonElement json, Type t, JsonDeserializationContext 
      jsonDeserializationContext) throws JsonParseException{ 
     try{ 
      // first, get the parent 
      MyDeserialParent parent = gson.fromJson(json, MyDeserialParent.class); 
      // get the child using the type parameter 
      String type = ((JsonObject)json).get("type").getAsString(); 
      parent.mSerialChild = gson.fromJson(json, CHILDREN_TO_TYPETOKEN.get(type)); 
      return parent; 

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

Bemerkungen:

  • der Brauch-Adapter auf dem gsonBuilder
  • , wenn Sie für Ihre Kinder einige benutzerdefinierte Gson Eigenschaften müssen müssen registriert sein, können Sie das Gson Objekt im Konstruktor von MyParentAdapter passieren kann, seit jetzt benutzt es den Standard;
  • Kinder und Eltern müssen Attribute mit unterschiedlichen Namen haben;
  • Jeder neue Typ muss der Karte mit der entsprechenden Klasse hinzugefügt werden.

Komplettes Beispiel

Main:

public class DeserializeExample{ 

    MyDeserialParent[] myDeserialParents; 

    static String json = "{\n" + 
      " \"myDeserialParents\" : [\n" + 
      "  {\n" + 
      "   \"otherProp\": \"lala\"," + 
      "   \"type\": \"value\", //used in a TypeAdapter to choose child implementation\n" + 
      "   \"childProp1\": \"1\",\n" + 
      "   \"childProp2\": \"2\"\n" + 
      "   }\n" + 
      "  ]\n" + 
      "}"; 


    public static void main(String[] args){ 
     Gson gson = new GsonBuilder().registerTypeAdapter(MyDeserialParent.class, new MyParentAdapter()).create(); 
     DeserializeExample result = gson.fromJson(json, DeserializeExample.class); 
     System.out.println(gson.toJson(result)); 
     // output: 
     // {"myDeserialParents":[{"mSerialChild":{"childProp1":"1","childProp2":"2"},"otherProp":"lala"}]} 
    }//end main 

}//end class 

Parent:

class MyDeserialParent<T extends MyChildInterface>{ 

    MyChildInterface mSerialChild; 
    //some other fields (not 'type') 
    String otherProp; 
} 

Kind:

public class MyChild1 implements MyChildInterface { 
    String childProp1; 
    String childProp2; 
}//end class 
+0

Dies ist sehr nah an meiner eigenen Lösung, nicht die andere Lösung, die ich gesucht habe, sondern eine gute Antwort, die die meisten Menschen, die hierher kommen werden, decken wird –