2016-02-09 17 views
13

mam mniej więcej następującą strukturęUtworzenie obiektu podrzędnego za pomocą params z obiektu macierzystego, który jest deserializowany za pomocą GSON i używa generycznych?

class MyDeserialParent<T extends MyChildInterface> { 

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

} 

Ale to rozszeregować z messy struktury JSON dwie właściwości dziecka są zwrócone na węźle nadrzędnym jak następuje.

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

Oczywiście to nie pozwala mi tylko adnotacji mSerialChild z SerializedName i pozwalając TypeAdapter pracy jej magii. Więc mam nadzieję, że to się stanie, gdy MyDeserialParent jest deserializowany, użyj "typ", aby znaleźć właściwą klasę betonu MyChildInterface i utworzyć nową z wykorzystaniem childProp1 i childProp2 jako parametrów konstruktora. Nie wiem, jak to osiągnąć.

mogę sobie wyobrazić, używając TypeAdapter (JsonDeserializer) dla MyDeserialParent aw deserialize uzyskać pole typu (a także dwie właściwości podrzędne), następnie instancję właściwą betonu dla MyChildInterface siebie.

To oznacza, że ​​muszę utworzyć swoją klasę MyDeserialParent (z context.deserialize(json, MyDeserialParent.class)) i wywołać program ustawiający z instancją MyChildInterface. To źle się czuje, tak jakbym czegoś przegapił. Czy istnieje lepszy sposób?

Czy istnieje również sposób na określenie generycznych (T na MyDeserialParent), jeśli ręcznie utworzyć obiekt nadrzędny również? lub Czy Typ Erasure oznacza, że ​​nie ma sposobu, aby to zrobić? (To pytanie jest mniej ważne, ponieważ wiem, że mogę uzyskać bezpieczeństwo typu, jeśli używam konkretnych podtypów MyDeserialParent, które już teraz sugerują T, ale chciałbym tego uniknąć).

Odpowiedz

3

Oczywiście potrzebujesz niestandardowego TypeAdapter. Ale skomplikowane części są:

  • klasa dominująca jest rodzajowy jeden
  • mSerialChild nie jest typu T, ale typu MyChildInterface
  • chcemy uniknąć parsowania ręcznie JSON dla każdej klasy dziecięcej i móc dodawać właściwości do rodzica bez konieczności modyfikowania całego kodu.

Mając to na uwadze, otrzymałem następujące rozwiązanie.

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

Uwagi:

  • adapter niestandardowy muszą być zarejestrowani na gsonBuilder
  • jeśli potrzebujesz niestandardowych właściwości gson dla dzieci, można przekazać obiekt Gson w konstruktorze MyParentAdapter, ponieważ teraz używa domyślnej;
  • dzieci i rodzic muszą mieć atrybuty o różnych nazwach:;
  • każdy nowy typ musi zostać dodany do mapy z odpowiednią klasą.

Kompletny przykład

główne:

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 

nadrzędny:

class MyDeserialParent<T extends MyChildInterface>{ 

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

dziecko:

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

To bardzo bliskie mojemu własnemu rozwiązaniu, a nie inne rozwiązanie, którego szukałem, ale dobra odpowiedź, która obejmie większość osób, które przychodzą tutaj szukając –