Pregunta Crear una instancia de objeto hijo con params del objeto padre que está siendo deserializado con GSON y usar genéricos?


Tengo aproximadamente la siguiente estructura

class MyDeserialParent<T extends MyChildInterface> {

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

}

Pero está deserializado a partir de una estructura JSON desordenada, las dos propiedades del elemento secundario se devuelven en el nodo padre de la siguiente manera.

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

Obviamente, esto me impide anotar mSerialChild con SerializedName y dejando un TypeAdapter trabaja su magia Entonces, lo que espero hacer es cuando MyDeserialParent se deserializa el uso de "tipo" para encontrar la clase concreta correcta de MyChildInterface y hacer uno nuevo usando childProp1 y childProp2 como params para el constructor. No sé cómo hacer esto.

Me puedo imaginar usando un TypeAdapter (JsonDeserializer) para MyDeserialParent y en deserialize obtener el campo de tipo (así como las dos propiedades secundarias), luego instanciar el concreto correcto para MyChildInterface mí mismo.

Esto significa que tengo que crear mi MyDeserialParent clase (con context.deserialize(json, MyDeserialParent.class)) y llamar a un colocador con MyChildInterface ejemplo. Eso se siente mal como si me falta algo. ¿Hay una mejor manera?

¿También hay una forma de especificar los genéricos (T en MyDeserialParent) si también creo manualmente el objeto principal? o ¿Significa Erasure significa que no hay forma de hacer esto? (Esta pregunta es menos importante porque sé que puedo obtener seguridad de tipo si uso subtipos específicos de MyDeserialParent que ya infieren T en cambio, pero me gustaría evitarlo)


13
2018-02-09 15:06


origen


Respuestas:


Obviamente necesitas una costumbre TypeAdapter. Pero las partes difíciles son:

  • su clase de padres es genérica
  • mSerialChild no es de tipo T, pero de tipo MyChildInterface
  • queremos evitar analizar a mano el json para cada clase secundaria y poder agregar propiedades al padre sin tener que modificar todo el código.

Teniendo esto en cuenta, terminé con la siguiente solución.

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

observaciones:

  • el adaptador personalizado debe estar registrado en gsonBuilder
  • si necesita algunas propiedades gson personalizadas para sus hijos, puede pasar el Gson objeto en el constructor de MyParentAdapter, ya que ahora usa el predeterminado;
  • niños y padres debe tener atributos con nombres distintos;
  • cada tipo nuevo debe agregarse al mapa con la clase correspondiente.

Ejemplo completo

Principal:

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

Padre:

class MyDeserialParent<T extends MyChildInterface>{

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

niño:

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

3
2018-03-01 11:31