Deserialización GSON con tipos genéricos y nombres genéricos de campos
Digamos que tenemos una estructura como esta:
JSON:
- Pasando el objeto JSON en la URL del servicio Web RESTFUL en Android?
- ¿Cómo enviar un objeto JSON a través de HttpClient Request con Android?
- Retrofit + OkHttp + GZIP-ed JSON
- Devolver una gota con json
- Solicitud de excepción de Android en IOException al recuperar JSON de URL
{ "body": { "cats": [{ "cat": { "id": 1, "title": "cat1" } }, { "cat": { "id": 2, "title": "cat2" } }] } }
Y POJO correspondiente:
Clase de respuesta
private final Body body;
Clase corporal
private final Collection<CatWrapper> cats
CatWrapper.class
private final Cat cat
Clase de gato
private final int id; private final String title;
Pero digamos ahora que tenemos la misma estructura, pero en vez de Cat
recibimos truck
{ "body": { "trucks": [{ "truck": { "id": 1, "engine": "big", "wheels" : 12 } }, { "truck": { "id": 2, "engine": "super big", "wheels" : 128 } }] } }
Estoy usando GSON (por defecto retrofit json parser) en Android
, considere esto al dar soluciones.
Podríamos usar genéricos aquí para que la respuesta se vea así:
private final Body<ListResponse<ItemWrapper<T>>
pero no podemos, ya que los nombres de campo son específicos de una clase.
PREGUNTA:
¿Qué puedo hacer para serializarlo de forma automática sin crear tantas clases clásicas? Realmente no necesito clases separadas como BodyCat
, BodyTruck
, BodyAnyOtherClassInThisStructure
y estoy buscando una manera de evitar tenerlos.
@EDITAR:
He cambiado clases (gato, perro -> gato, camión) debido a la confusión de la herencia, las clases usadas aquí como ejemplo NO se extiende una a otra
- Tipo de incompatibilidad de JSON o org.json.jsonecxeption
- Alternativa del método Jsoup.parse ()
- Parse Android Volley respuesta JSONArray
- Org.json.JSONObject no se puede convertir en JSONArray en android
- Que cliente es mejor HttpURLConnection o HttpClient en android?
- Excepción de JsonReader en Android mientras se analizan datos JSON grandes
- Exectuing http POST devuelve HTML en lugar de JSON
- El servidor GCM de Google devuelve Error no autorizado 401
Una idea sería definir un deserializador genérico personalizado. Su tipo genérico representará la clase concreta de los elementos de la lista envueltos en una instancia de Body
.
Asumiendo las siguientes clases:
class Body<T> { private List<T> list; public Body(List<T> list) { this.list = list; } } class Cat { private int id; private String title; ... } class Truck { private int id; private String engine; private int wheels; ... }
El deserializador asume que la estructura del json es siempre la misma, en el sentido de que usted tiene un objeto que contiene un objeto llamado "body". También asume que el valor en el primer par de valores-clave de este cuerpo es una lista.
Ahora, para cada elemento de la matriz json, necesitamos buscar de nuevo el objeto interno asociado con cada clave. Deserializamos este valor y lo ponemos en la lista.
class CustomJsonDeserializer<T> implements JsonDeserializer<Body<T>> { private final Class<T> clazz; public CustomJsonDeserializer(Class<T> clazz) { this.clazz = clazz; } @Override public Body<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject body = json.getAsJsonObject().getAsJsonObject("body"); JsonArray arr = body.entrySet().iterator().next().getValue().getAsJsonArray(); List<T> list = new ArrayList<>(); for(JsonElement element : arr) { JsonElement innerElement = element.getAsJsonObject().entrySet().iterator().next().getValue(); list.add(context.deserialize(innerElement, clazz)); } return new Body<>(list); } }
Para el paso final, sólo tiene que crear el tipo correspondiente, instanciar y registrar el adaptador en el analizador. Por ejemplo para camiones:
Type truckType = new TypeToken<Body<Truck>>(){}.getType(); Gson gson = new GsonBuilder() .registerTypeAdapter(truckType, new CustomJsonDeserializer<>(Truck.class)) .create(); Body<Truck> body = gson.fromJson(new FileReader("file.json"), truckType);
Incluso puede devolver la lista directamente desde el adaptador si desea deshacerse de la clase Body
.
Con los camiones obtendrá [1_big_12, 2_super big_128]
como salida y [1_cat1, 2_cat2]
con los gatos.
Yo estaría utilizando este enfoque:
public class Body { private List<Animal> animals; } } public class Animal {} public class Dog extends Animal {} public class Cat extends Animal {}
En este caso usted tendrá la serialización w / o cualquier boilerplates, excepto hecho que usted tiene que utilizar Gson TypeAdapter
para Animal
clase Animal
, como:
Gson gson = new GsonBuilder() .registerTypeAdapter(Animal.class, new AnimalSerializer()) .create();
Donde TypeAdapter debería verse como:
public class AnimalSerializer implements JsonSerializer<Animal>, JsonDeserializer<Animal> { private static final String CLASS_META_KEY="clz"; @Override public JsonElement serialize(Animal src, Type typeOfSrc, JsonSerializationContext context) { JsonElement element=null; if (src == null) { return element; } if(src instanceof Cat) element = context.serialize(src, Cat.class); else if(src instanceof Dog) element = context.serialize(src, Dog.class); else throw new IllegalArgumentException("Unspecifiad class serializer for "+src.getClass().getName()); element.getAsJsonObject().addProperty(CLASS_META_KEY, src.getClass().getCanonicalName()); return element; } @Override public Field deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { Class<?> clz; Animal animal; JsonObject object = jsonElement.getAsJsonObject(); if (object.has(CLASS_META_KEY)) { String className = object.get(CLASS_META_KEY).getAsString(); try { clz = Class.forName(className); } catch (Exception e) { Log.e(TAG, "Can't deserialize class="+className,e); clz = Animal.class; } animal = context.deserialize(jsonElement, clz); } else { animal = context.deserialize(jsonElement, typeOfT); } return animal; } }
public class body { private List<cats> cats; private List<dogs> dogs; public class cats { private list<cat> cat; } public class dogs { private list<dog> dog; } }
Esto realmente no reduce el código tradicional, pero debe impedir que tenga una clase de cuerpo separado con listas de clases que son sólo listas de sus animales reales. Debe hacer para que usted apenas tenga su clase del cuerpo, y entonces una clase para cada animal con él es stats.