¿Cómo manejar Dynamic JSON en Retrofit?


82

Estoy usando la biblioteca de red eficiente de actualización, pero no puedo manejar Dynamic JSON que contiene un prefijo único responseMessageque cambia objectaleatoriamente, el mismo prefijo ( responseMessage) cambia a String en algunos casos (dinámicamente).

Formato JSON Objeto de respuesta Mensaje:

{
   "applicationType":"1",
   "responseMessage":{
      "surname":"Jhon",
      "forename":" taylor",
      "dob":"17081990",
      "refNo":"3394909238490F",
      "result":"Received"
   }

}

responseMessage El formato JSON cambia dinámicamente para escribir cadena:

 {
       "applicationType":"4",
       "responseMessage":"Success"          
 }

Mi problema es que, dado que la modificación tiene un JSONanálisis integrado , ¡tengo que asignar un solo POJO por solicitud! pero la REST-API, desafortunadamente, se basa en JSONrespuestas dinámicas . ¡El prefijo cambiará de una cadena a un objeto aleatoriamente en los métodos de éxito (...) y fracaso (...) !

void doTrackRef(Map<String, String> paramsref2) {
    RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint("http://192.168.100.44/RestDemo").build();



    TrackerRefRequest userref = restAdapter.create(TrackerRefRequest.class);
    userref.login(paramsref2,
            new Callback<TrackerRefResponse>() {
                @Override
                public void success(
                        TrackerRefResponse trackdetailresponse,
                        Response response) {

                    Toast.makeText(TrackerActivity.this, "Success",
                    Toast.LENGTH_SHORT).show();

                }

                @Override
                public void failure(RetrofitError retrofitError) {


                    Toast.makeText(TrackerActivity.this, "No internet",
                        Toast.LENGTH_SHORT).show();
                }


            });
}

Pojo:

public class TrackerRefResponse {


private String applicationType;

    private String responseMessage;          //String type

//private ResponseMessage responseMessage;  //Object of type ResponseMessage

//Setters and Getters


}

En el código anterior, POJO TrackerRefResponse.java prefix responseMessage se establece en una cadena u objeto de tipo responseMessage, por lo que podemos crear el POJO con la variable ref con el mismo nombre (conceptos básicos de Java :)), así que estoy buscando la misma solución para la dinámica JSONen Retrofit. Sé que este es un trabajo muy fácil en clientes http normales con tareas asíncronas, ¡pero no es la mejor práctica en el JSONanálisis REST-Api ! mirando los puntos de referencia de rendimiento siempre Volley o Retrofit es la mejor opción, ¡pero no he logrado manejar la dinámica JSON!

Posible solución lo sé

  1. Utilice la tarea asyc antigua con análisis de cliente http. :(

  2. Intente convencer al desarrollador backend de RESTapi.

  3. Cree un cliente de Retrofit personalizado :)


1
"Intente convencer al desarrollador de backend de RESTapi". me hizo el truco! lol! ;) (nb: yo también era el desarrollador de backend, ¡para convencerme a mí mismo!)
mrx

Respuestas:


38

Tarde para la fiesta, pero puedes usar un convertidor.

RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint("https://graph.facebook.com")
    .setConverter(new DynamicJsonConverter()) // set your static class as converter here
    .build();

api = restAdapter.create(FacebookApi.class);

Luego usa una clase estática que implementa el convertidor de retrofit:

static class DynamicJsonConverter implements Converter {

    @Override public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {
        try {
            InputStream in = typedInput.in(); // convert the typedInput to String
            String string = fromStream(in);
            in.close(); // we are responsible to close the InputStream after use

            if (String.class.equals(type)) {
                return string;
            } else {
                return new Gson().fromJson(string, type); // convert to the supplied type, typically Object, JsonObject or Map<String, Object>
            }
        } catch (Exception e) { // a lot may happen here, whatever happens
            throw new ConversionException(e); // wrap it into ConversionException so retrofit can process it
        }
    }

    @Override public TypedOutput toBody(Object object) { // not required
        return null;
    }

    private static String fromStream(InputStream in) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        StringBuilder out = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            out.append(line);
            out.append("\r\n");
        }
        return out.toString();
    }
}

He escrito este convertidor de muestra para que devuelva la respuesta Json como String, Object, JsonObject o Map <String, Object>. Obviamente, no todos los tipos de devolución funcionarán para todos los Json, y es seguro que hay margen de mejora. Pero demuestra cómo usar un convertidor para convertir casi cualquier respuesta a Json dinámico.


13
Ver RestAdaptereste ejemplo es para Retrofit 1. ¿Cómo implementaría el mismo convertidor en Retrofit 2?
androidtitan

1
ConversionException no está disponible en Retrofit 2 :(
mutkan

20

RestClient.java

import retrofit.client.Response;
public interface RestClient {
  @GET("/api/foo") Response getYourJson();
}

YourClass.java

RestClient restClient;

// create your restClient

Response response = restClient.getYourJson();

Gson gson = new Gson();
String json = response.getBody().toString();
if (checkResponseMessage(json)) {
  Pojo1 pojo1 = gson.fromJson(json, Pojo1.class);
} else {
  Pojo2 pojo2 = gson.fromJson(json, Pojo2.class);
}

Debe implementar el método "checkResponseMessage".


¿Cómo puedo hacer lo mismo en Retrofit 2?
Vadim Kotov

1
¿Qué es "checkResponseMessage"?
Shashank singh

15

Pruebe la deserialización personalizada usando gson-converterlo siguiente (respuesta actualizada para Retrofit 2.0)

Cree tres modelos como se muestra a continuación

ResponseWrapper

public class ResponseWrapper {

    @SerializedName("applicationType")
    @Expose
    private String applicationType;
    @SerializedName("responseMessage")
    @Expose
    private Object responseMessage;

    public String getApplicationType() {
        return applicationType;
    }

    public void setApplicationType(String applicationType) {
        this.applicationType = applicationType;
    }

    public Object getResponseMessage() {
        return responseMessage;
    }

    public void setResponseMessage(Object responseMessage) {
        this.responseMessage = responseMessage;
    }

}

ResponseMessage

public class ResponseMessage extends ResponseWrapper {

@SerializedName("surname")
@Expose
private String surname;
@SerializedName("forename")
@Expose
private String forename;
@SerializedName("dob")
@Expose
private String dob;
@SerializedName("refNo")
@Expose
private String refNo;
@SerializedName("result")
@Expose
private String result;

public String getSurname() {
    return surname;
}

public void setSurname(String surname) {
    this.surname = surname;
}

public String getForename() {
    return forename;
}

public void setForename(String forename) {
    this.forename = forename;
}

public String getDob() {
    return dob;
}

public void setDob(String dob) {
    this.dob = dob;
}

public String getRefNo() {
    return refNo;
}

public void setRefNo(String refNo) {
    this.refNo = refNo;
}

public String getResult() {
    return result;
}

public void setResult(String result) {
    this.result = result;
}

}

ResponseString

public class ResponseString extends ResponseWrapper {

}

UserResponseDeserializer (deserializador personalizado)

public class UserResponseDeserializer implements JsonDeserializer<ResponseWrapper> {
@Override
public ResponseWrapper deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {


        if (((JsonObject) json).get("responseMessage") instanceof JsonObject){
            return new Gson().fromJson(json, ResponseMessage.class);
        } else {
            return new Gson().fromJson(json, ResponseString.class);
        }

}
}

Implementación de Retrofit 2.0

Gson userDeserializer = new GsonBuilder().setLenient().registerTypeAdapter(ResponseWrapper.class, new UserResponseDeserializer()).create();


    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("base_url")
            .addConverterFactory(GsonConverterFactory.create(userDeserializer))
            .build();


    UserService request = retrofit.create(UserService.class);
    Call<ResponseWrapper> call1=request.listAllUsers();

    call1.enqueue(new Callback<ResponseWrapper>() {
        @Override
        public void onResponse(Call<ResponseWrapper> call, Response<ResponseWrapper> response) {
            ResponseWrapper responseWrapper=response.body();
            Log.i("DYNAMIC RESPONSE", String.valueOf(response.body().getResponseMessage()));
        }

        @Override
        public void onFailure(Call<ResponseWrapper> call, Throwable t) {
        }
    });

Bibliotecas utilizadas

compilar 'com.squareup.retrofit2: retrofit: 2.3.0'

compile 'com.squareup.retrofit2: converter-gson: 2.3.0'

***** Respuesta anterior (la respuesta anterior es más recomendada) *****

Cambia tu pojo así

public class TrackerRefResponse {

  private String applicationType;
  private Object responseMessage;

  public Object getResponseMessage() {
  return responseMessage;
  }

  public void setResponseMessage(Object responseMessage) {
  this.responseMessage = responseMessage;
 }
}

y cambiar el onResponse de retrofit así

 @Override
public void onResponse(Response<TrackerRefResponse > response) {
    if (response.isSuccess()) {
        if (response.getResponseMessage() instanceof String)
            {
            handleStringResponse();
         }
        else 
            {
            handleObjectResponse();
         }
    }
}

también puede consultar esta publicación para obtener más detalles sobre el análisis dinámico de json


¿Es realmente necesaria la clase ResponseWrapper? Creo que parece muy confuso. Necesito un convertidor en cualquier cosa menos en el objeto más alto en la jerarquía ...
RabbitBones22

1
puede evitar la clase contenedora si es confuso y probar este freshbytelabs.com/2018/12/…
Navneet Krishna

9

La respuesta aceptada me pareció demasiado complicada, la resuelvo de esta manera:

Call<ResponseBody> call = client.request(params);
call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Response<ResponseBody> response) {
        if (response.isSuccess()) {
            Gson gson = new Gson();
            ResponseBody repsonseBody = response.body().string();
            if (isEmail) {
                EmailReport reports = gson.fromJson(responseBody, EmailReport.class);
            } else{
                PhoneReport reports = gson.fromJson(repsonseBody, PhoneReport.class);
            }
        }
    }
    @Override
    public void onFailure(Throwable t) {
        Log.e(LOG_TAG, "message =" + t.getMessage());
    }
});

Este es solo un ejemplo en un intento de mostrarle cómo puede usar un modelo diferente.

La variable isEmailes solo un booleano para que su condición use el modelo apropiado.


¿Podrías explicar eso por favor? Este código no es descriptivo. ¿De dónde viene mType?
Desdroid

@Desdroid Simplifiqué el código y luego lo amplié con una explicación
meda

2
Aún así, creo que esto no ayudará si no conoce el Tipo de respuesta antes de realizar la llamada, que es el caso de la pregunta. Seguro que primero podría obtener el InputStream del cuerpo de respuesta, leer algunas líneas, determinar de qué tipo es el cuerpo y luego convertirlo. Pero no es tan simple como eso.
Desdroid

Estoy buscando la mejor manera de manejar diferentes tipos de devoluciones. Tu respuesta parecía bastante prometedora, pero no estaba seguro de dónde conocías al Tipo. Por eso quería que elaboraras eso;)
Desdroid

2
Creo que no funcionará porque Deserializer lanzará una excepción y alcanzará onFailure () no onResponse ()
Amt87

9

Cualquiera de sus posibles soluciones funcionará. Lo que también puede hacer es enviar el tipo de retorno de la interfaz de la API de Retrofit a la respuesta. Con esa respuesta, obtiene un cuerpo Inputstreamque puede convertir en un objeto JSON y leerlo como mejor le parezca.

Mire: http://square.github.io/retrofit/#api-declaration - bajo TIPO DE OBJETO DE RESPUESTA

Actualizado

Retrofit 2 ya está disponible y con algunos cambios en la documentación y la biblioteca.

Mire http://square.github.io/retrofit/#restadapter-configuration, hay un objeto de cuerpo de solicitud y respuesta que se puede usar.


6
Me temo que no puedo encontrar la sección que ha proporcionado, ¿hay algún sinónimo?
zionpi

7

Sé que llego muy, muy tarde a la fiesta. Tuve un problema similar y lo resolví así:

public class TrackerRefResponse {

    private String applicationType;
    // Changed to Object. Works fine with String and array responses.
    private Object responseMessage;

}

Literalmente acabo de cambiar para escribir a Objeto. Elegí este enfoque porque solo un campo en la respuesta era dinámico (para mí, mi respuesta fue mucho más complicada), por lo que usar un convertidor me habría complicado la vida. Usé Gson para trabajar con el objeto desde allí, dependiendo de si era un valor de cadena o matriz.

Espero que esto ayude a alguien que busque una respuesta simple :).


3

Si no fuera posible cambiar la API de backend, consideraría las siguientes variantes (si se usa Gson para convertir JSON).

  1. Podemos usar adaptadores de tipo Gson para crear un adaptador personalizado para el ResponseMessagetipo que decide dinámicamente cómo analizar el JSON inoming (usando algo como if (reader.peek() == JsonToken.STRING)).

  2. Coloque algo de metainformación que describa el tipo de respuesta en un encabezado HTTP y utilícelo para determinar qué tipo de información debe enviarse a la instancia de Gson.



0

Yo también corrí de este problema. pero no estoy seguro de si este fue tu caso (estoy usando Retrofit2)

en mi caso, necesito manejar los mensajes de error y éxito.

Sobre el éxito

{
"call_id": 1,
"status": "SUCCESS",
"status_code": "SUCCESS",
"result": {
    "data1": {
        "id": "RFP2UjW7p8ggpMXzYO9tRg==",
        "name": "abcdef",
        "mobile_no": "96655222",
        "email": ""
    },
    "data2": [
        {
            "no": "12345"
        },
        {
            "no": "45632"
        }
    ]
}
}

En caso de error,

{
"call_id": 1,
"status": "FAILED",
"status_code": "NO_RECORDS",
"error": {
    "error_title": "xxx",
    "error_message": "details not found"
}
}

para esto acabo de crear otro POJO Error,

public class ValidateUserResponse {
@SerializedName("call_id")
public String callId;
@SerializedName("status")
public String status;
@SerializedName("status_code")
public String statusCode;
@SerializedName("result")
public ValidateUserResult result;
@SerializedName("error")
public Error error;
}

Error.java

public class Error {
@SerializedName("error_title")
public String errorTitle;
@SerializedName("error_message")
public String errorMessage;
}

ValidateUser.java

public class ValidateUserResult {

@SerializedName("auth_check")
public String authCheck;
@SerializedName("data1")
public Data1 data1;
@SerializedName("data2")
public List<Data2> data2;
}

en el caso anterior, si la resultclave en json contiene data1, data2, entonces ValidateUserResult.javase inicializa. si hay un error, la Error.javaclase se inicializa.


0

Sé que llego tarde, pero solo quiero compartir mi pensamiento. Estaba trabajando en un proyecto en el que estoy escribiendo un método. El método utiliza modernización para obtener datos del servidor. Dado que otros desarrolladores de mi empresa usarán este método, no podría usar una POJOclase (en su ejemplo, la TrackerRefResponseclase). Entonces usé JsonObject/Object gusta esto:

interfaz APIService.java

public class APIService{
    @FormUrlEncoded
    @POST
    Call<JsonObject> myPostMethod(@Url String url, @Field("input") String input);
}

Luego, en mi método, escribí esto:

Call<JsonObject> call = RetrofitClient.getAPIService().establishUserSession(post_request_url, someParameter);

call.enqueue(new Callback<JsonObject>() {
            @Override
            public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
                JsonObject jsonObject = response.body();
                String jsonString = response.body().toString();
                // then do your stuff. maybe cast to object using a factory pattern  
    }
// rest of the code
}

También puede utilizar en Objectlugar de 'JsonObject`. Más tarde, cuando sepa qué tipo de respuesta es, tal vez pueda convertir esto en el objeto deseado.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.