Convertidor de gson retrofit para json anidado con diferentes objetos

Tengo la estructura JSON como sigue –

{ "status": true, "message": "Registration Complete.", "data": { "user": { "username": "user88", "email": "[email protected]", "created_on": "1426171225", "last_login": null, "active": "1", "first_name": "User", "last_name": "", "company": null, "phone": null, "sign_up_mode": "GOOGLE_PLUS" } } } 

El formato anterior es común. Sólo data clave de data puede contener diferentes tipos de información como user , product , invoice , etc.

Quiero mantener data claves de status , message y data iguales en cada respuesta de descanso. data se tratarán según el status y el message se mostrará al usuario.

Por lo tanto, básicamente, el formato anterior es deseado en todos los apis. Sólo la información dentro de data clave de data será diferente cada vez.

Y he configurado una clase siguiente y lo configuro como convertidor de gsonMyResponse.java

 public class MyResponse<T> implements Serializable{ private boolean status ; private String message ; private T data; public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } } 

Deserializer.java

 class Deserializer<T> implements JsonDeserializer<T>{ @Override public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException{ JsonElement content = je.getAsJsonObject(); // Deserialize it. You use a new instance of Gson to avoid infinite recursion to this deserializer return new Gson().fromJson(content, type); } } 

Y lo usó como sigue –

 GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); gsonBuilder.registerTypeAdapter(MyResponse.class, new Deserializer<MyResponse>()); ...... ..... .... restBuilder.setConverter(new GsonConverter(gsonBuilder.create())); 

La interfaz de servicio es la siguiente –

 @POST("/register") public void test1(@Body MeUser meUser, Callback<MyResponse<MeUser>> apiResponseCallback); @POST("/other") public void test2(Callback<MyResponse<Product>> apiResponseCallback); 

Problema

Puedo acceder a los campos de status y message desde la devolución de llamada interna. Pero la información dentro de data clave de data no se analiza y el modelo como MeUser y Product siempre se devuelve como vacío.

Si cambio la estructura de json a siguiente encima del código trabaja perfectamente –

 { "status": true, "message": "Registration Complete.", "data": { "username": "user88", "email": "[email protected]", "created_on": "1426171225", "last_login": null, "active": "1", "first_name": "User", "last_name": "", "company": null, "phone": null, "sign_up_mode": "GOOGLE_PLUS" } } 

¿Cómo puedo hacerlo funcionar especificando una clave separada dentro de data objeto de data y analizarlo con éxito?

Si puedo sugerir que cambie algo en json es que usted tiene que agregar en un nuevo campo que define el tipo de datos, por lo que json debería tener el siguiente aspecto:

 { "status": true, "message": "Registration Complete.", "dataType" : "user", "data": { "username": "user88", "email": "[email protected]", "created_on": "1426171225", "last_login": null, "active": "1", "first_name": "User", "last_name": "", "company": null, "phone": null, "sign_up_mode": "GOOGLE_PLUS" } } 

La clase MyResponse tiene que tener un nuevo DataType archivado, por lo que debería verse como a continuación:

 public class MyResponse<T> implements Serializable{ private boolean status ; private String message ; private DataType dataType ; private T data; public DataType getDataType() { return dataType; } //... other getters and setters } 

El DataType es un enum que define el tipo de datos. Tienes que pasar Data.class como param en el constructor. Para todos los tipos de datos hay que crear nuevas clases. DataType enum debe verse como a continuación:

 public enum DataType { @SerializedName("user") USER(MeUser.class), @SerializedName("product") Product(Product.class), //other types in the same way, the important think is that //the SerializedName value should be the same as dataType value from json ; Type type; DataType(Type type) { this.type = type; } public Type getType(){ return type; } } 

El desarializator de Json debería tener el siguiente aspecto:

 public class DeserializerJson implements JsonDeserializer<MyResponse> { @Override public MyResponse deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { JsonObject content = je.getAsJsonObject(); MyResponse message = new Gson().fromJson(je, type); JsonElement data = content.get("data"); message.setData(new Gson().fromJson(data, message.getDataType().getType())); return message; } } 

Y cuando se crea RestAdapter , en la línea donde se registra Deserializator, debe usar esto:

  .registerTypeAdapter(MyResponse.class, new DeserializerJson()) 

Otras clases (tipos de datos) que define como POJO estándar para Gson en clases separadas.

Su problema es porque el atributo de data se define como T que se espera que sea de los tipos MeUser , Product , etc, pero es en realidad de un objeto que tiene atributo interno como user . Para resolver esto, es necesario introducir otro nivel de clases que tenga los atributos requeridos de user , product , invoice , etc. Esto se puede lograr fácilmente usando clases internas estáticas.

 public class MeUser{ private User user; public static class User{ private String username; //add other attributes of the User class } } 

Puede ser un poco fuera de tema, pero ¿qué sucede si el objeto interno contiene una propiedad Date? Mi TypeAdapter tiene este aspecto:

  .registerTypeAdapter(Date.class, new DateDeserializer()) .registerTypeAdapter(GenericNotificationResponse.class, new NotificationDeserializer()) .setDateFormat("yyyy-MM-dd'T'HH:mm:ss") .create(); 

Pero debido al análisis que se hace por este: message.setData(new Gson().fromJson(data, message.getDataType().getType()));

Se lanzará un error siempre que intentará deserializar la propiedad Date. ¿Hay una solución rápida para esto?

EDIT: Marcó la respuesta como aceptada, definitivamente 🙂 me ayudó a arreglar mi problema. Pero ahora hay este problema con el deserializador de fecha.

  • Cómo enviar datos JSON como Cuerpo usando Retrofit android
  • RoboSpice lanzando excepciones okhttp
  • POST Multipart Form Datos usando Retrofit 2.0 incluyendo imagen
  • Android aar dependencias
  • Obtención de información de encabezado con RXJava y Retrofit
  • Uso de MultipartTypedOutput
  • Cómo crear un objeto retrofit.Response durante las pruebas de unidad con Retrofit 2
  • ¿Cómo puedo interceptar un objeto observable y modificarlo en RxJava antes de volver al suscriptor?
  • Retrofit 2 RxJava - Gson - Deserialización "Global", tipo respuesta de cambio
  • Obtenga un objeto json simple usando retrofit 2
  • Retrofit + RxJava no puede almacenar en caché las respuestas, los encabezados de respuesta sospechados
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.