webentwicklung-frage-antwort-db.com.de

Wie gehe ich mit Dynamic JSON im Retrofit um?

Ich benutze eine effiziente Nachrüstung der Netzwerkbibliothek, kann aber nicht mit Dynamic JSON umgehen, das ein einzelnes Präfix responseMessage enthält, das zufällig in object geändert wird. In einigen Fällen ändert sich dasselbe Präfix (responseMessage) in String (dynamisch). 

Json-Format Antwortobjekt:

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

}

responseMessage Das Json-Format ändert sich dynamisch in den Typ string:

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

Das Problem für mich ist, da Retrofit JSON-Analyse eingebaut hat, müssen wir einen einzelnen POJO pro Anfrage zuweisen! Die REST-API ist jedoch leider mit dynamischen JSON-Antworten aufgebaut. Das Präfix wird in success (...) - und failure (...) -Methode zufällig in Objekt umgewandelt. 

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


}

Im obigen Code POJO ist das TrackerRefResponse.Java-Präfix responseMessage auf string oder ein Objekt vom Typ responseMessage gesetzt, sodass wir den POJO mit der ref-Variable mit demselben Namen (Java-Grundlagen :) erstellen können. Deshalb suche ich in Retrofit nach der gleichen Lösung für dynamisches JSON Ich weiß, dass dies bei normalen http-Clients mit async-Task eine sehr einfache Aufgabe ist, aber es ist nicht die bewährte Methode in der REST-Api JSON-Analyse! Wenn man sich die Performance ansieht, ist Benchmarks immer Volley oder Retrofit die beste Wahl, aber ich bin mit der dynamischen Ausführung von _JSON nicht zufrieden! 

Mögliche Lösung, die ich kenne 

  1. Verwenden Sie die alte asyc-Task mit der HTTP-Client-Analyse. :( 

  2. Versuchen Sie, den RESTapi-Backend-Entwickler zu überzeugen.

  3. Erstellen Sie einen benutzerdefinierten Retrofit-Client :)

68
LOG_TAG

Spät zur Party, aber Sie können einen Konverter verwenden.

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

Dann verwenden Sie eine statische Klasse, die den Konverter von Retrofit implementiert:

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

Ich habe diesen Beispielkonverter geschrieben, damit er die Json-Antwort entweder als String, Object, JsonObject oder Map <String, Object> zurückgibt. Offensichtlich funktionieren nicht alle Rückgabetypen für jeden Json und es gibt durchaus Verbesserungsbedarf. Es zeigt jedoch, wie ein Konverter verwendet wird, um fast jede Antwort in einen dynamischen Json zu konvertieren.

35
Oliver Hausler

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

Sie müssen die Methode "checkResponseMessage" implementieren.

17
Yuki Yoshida

Versuchen Sie eine benutzerdefinierte Deserialisierung mit gson-converter wie unten (aktualisierte Antwort für Retrofit 2.0)

Erstellen Sie drei Modelle wie unten gezeigt

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 (benutzerdefinierter Deserializer)

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

}
}

Retrofit 2.0-Implementierung

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

Verwendete Bibliotheken

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

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

***** Vorherige Antwort (obige Antwort ist empfehlenswerter) *****

Ändere dein Pojo so

public class TrackerRefResponse {

  private String applicationType;
  private Object responseMessage;

  public Object getResponseMessage() {
  return responseMessage;
  }

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

und ändern Retrofit onResponse so

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

sie können auch in diesem Beitrag finden Sie weitere Informationen zum dynamischen Parsing von JSON

15
Navneet Krishna

Jede Ihrer möglichen Lösungen wird funktionieren. Sie können auch den Rückgabetyp der Retrofit-API-Schnittstelle an response senden. Mit dieser Antwort erhalten Sie einen Körper Inputstream, den Sie in ein JSON-Objekt konvertieren und nach Belieben lesen können.

Siehe: http://square.github.io/retrofit/#api-declaration - unter RESPONSE OBJECT TYPE

Aktualisierte

Retrofit 2 ist jetzt verfügbar und damit einige Änderungen an der Dokumentation und Bibliothek.

Siehe http://square.github.io/retrofit/#restadapter-configuration Dort sind Anfrage- und Antwort-Body-Objekte, die verwendet werden können.

10
Justin Slade

Die akzeptierte Antwort erschien mir zu kompliziert, ich löse sie so:

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

Dies ist nur ein Beispiel, um zu zeigen, wie Sie verschiedene Modelle verwenden können.

Die Variable isEmail ist nur ein boolescher Wert, damit Ihre Bedingung das entsprechende Modell verwendet.

8
meda

Ich weiß, ich bin sehr spät zur Party. Ich hatte ein ähnliches Problem und habe es so gelöst:

public class TrackerRefResponse {

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

}

Ich habe buchstäblich gerade in Object umgestellt. Ich habe mich für diesen Ansatz entschieden, weil nur ein Feld in der Antwort dynamisch war (für mich war meine Antwort viel komplizierter), so dass die Verwendung eines Konverters das Leben schwierig gemacht hätte. Verwendete Gson, um von dort aus mit dem Objekt zu arbeiten, je nachdem, ob es sich um einen String- oder Array-Wert handelt.

Hoffe, das hilft jemandem, der nach einer einfachen Antwort sucht :).

6
Lunchbox

Wenn die Backend-API nicht geändert werden konnte, würde ich die folgenden Varianten in Betracht ziehen (falls Gson zum Konvertieren von JSON verwendet wird).

  1. Wir können Gson typ-Adapter verwenden, um einen benutzerdefinierten Adapter für ResponseMessage-Typ zu erstellen, der dynamisch entscheidet, wie der eingehende JSON (mit etwas wie if (reader.peek() == JsonToken.STRING)) analysiert wird.

  2. Fügen Sie einige Meta-Informationen, die den Antworttyp beschreiben, in einen HTTP-Header ein, und bestimmen Sie, welche Typinformationen der Gson-Instanz zugeführt werden müssen.

3
Roman Mazur

Zusätzlich zu dem, was Sie gesagt haben -

Verwenden Sie Callback Dann können Sie die Felder mit der regulären get-Methode abrufen Weitere Informationen erhalten Sie, indem Sie den Javadoc von gson durchgehen.

http://google-gson.googlecode.com/svn/tags/1.2.3/docs/javadocs/com/google/gson/JsonObject.html

1
Sugesh

Ich habe auch dieses Problem ausgeführt ... aber ich bin nicht sicher, ob dies Ihr Fall war (ich benutze Retrofit2)

in meinem Fall muss ich Fehler- und Erfolgsmeldungen behandeln.

Auf Erfolg

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

Auf Fehler,

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

dafür habe ich gerade eine weitere POJO Error erstellt,

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

wenn im obigen Fall der result-Schlüssel auf json data1 enthält, data2, dann wird der ValidateUserResult.Java initialisiert. Wenn der Fehler auftritt, wird die Error.Java-Klasse initialisiert.

0
Akhil T Mohan