Wie gehe ich mit Dynamic JSON in Retrofit um?

82

Ich verwende die effiziente Netzwerkbibliothek für die Nachrüstung, kann jedoch Dynamic JSON nicht verarbeiten, das ein einzelnes Präfix enthält, responseMessagedas sich objectzufällig responseMessageändert. In einigen Fällen ändert sich dasselbe Präfix ( ) in String (dynamisch).

Json-Format Objekt der AntwortMessage:

{
   "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"          
 }

Mein Problem ist JSON, dass ich, da die Nachrüstung über eine integrierte Analyse verfügt, pro Anfrage ein einzelnes POJO zuweisen muss! Leider basiert die REST-API auf dynamischen JSONAntworten. Das Präfix ändert sich sowohl bei erfolgreichen (...) als auch bei fehlgeschlagenen (...) Methoden zufällig von Zeichenfolge zu Objekt !

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 ist das POJO TrackerRefResponse.java-Präfix responseMessage auf einen String oder ein Objekt vom Typ responseMessage festgelegt, sodass wir das POJO mit der Ref-Variablen mit demselben Namen erstellen können (Java-Grundlagen :)). Daher suche ich nach derselben Lösung für Dynamic JSONin Retrofit. Ich weiß, dass dies bei normalen http-Clients mit asynchroner Aufgabe sehr einfach ist, aber es ist nicht die beste Vorgehensweise beim JSONParsen von REST-Api ! Betrachtet man die Leistung Benchmarks immer Volley oder Retrofit ist die beste Wahl, aber ich bin nicht in der Lage, dynamisch zu handhaben JSON!

Mögliche Lösung, die ich kenne

  1. Verwenden Sie die alte Asyc-Task beim Parsen des http-Clients. :(

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

  3. Benutzerdefinierten Retrofit-Client erstellen :)

LOG_TAG
quelle
1
"Versuchen Sie, den RESTapi-Backend-Entwickler zu überzeugen." hat den Trick für mich gemacht! lol! ;) (nb: Ich war auch der Backend-
Entwickler

Antworten:

38

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 Nachrüstkonverter 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 so geschrieben, dass 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 sicher Raum für Verbesserungen. Es zeigt jedoch, wie mit einem Konverter fast jede Antwort in dynamisches Json konvertiert wird.

Oliver Hausler
quelle
13
Sehen Sie RestAdapterdieses Beispiel ist für Retrofit 1. Wie würden Sie den gleichen Wandler in Retrofit 2 implementieren?
AndroidTitan
1
ConversionException ist nicht verfügbar in 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);
}

Sie müssen die Methode "checkResponseMessage" implementieren.

Yuki Yoshida
quelle
Wie kann ich dasselbe in Retrofit 2 tun?
Vadim Kotov
Was ist "checkResponseMessage"?
Shashank singh
15

Versuchen Sie die benutzerdefinierte Deserialisierung gson-converterwie folgt (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 Deserialiser)

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

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

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

***** Vorherige Antwort (obige Antwort wird eher empfohlen) *****

Ä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 Sie die onResponse der Nachrüstung wie folgt

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

Sie können diesen Beitrag auch überprüfen, um weitere Informationen zum dynamischen JSON-Parsing zu erhalten

Navneet Krishna
quelle
Ist die ResponseWrapper-Klasse wirklich notwendig? Ich finde es sieht sehr verwirrend aus. Ich brauche einen Konverter für alles andere als das höchste Objekt in der Hierarchie ...
RabbitBones22
1
Sie können die Wrapper-Klasse vermeiden, wenn sie verwirrend ist, und versuchen Sie dies mit freshbytelabs.com/2018/12/…
Navneet Krishna
9

Die akzeptierte Antwort schien mir zu kompliziert, ich löse sie folgendermaßen:

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 Ihnen zu zeigen, wie Sie verschiedene Modelle verwenden können.

Die Variable isEmailist nur ein Boolescher Wert für Ihre Bedingung, um das entsprechende Modell zu verwenden.

meda
quelle
Könnten Sie das bitte näher erläutern? Dieser Code ist nicht beschreibend. Woher kommt mType?
Desdroid
@ Desdroid Ich habe den Code vereinfacht und dann mit Erklärung erweitert
meda
2
Trotzdem glaube ich, dass dies nicht hilft, wenn Sie den Antworttyp vor dem Anruf nicht kennen, was in der Frage der Fall ist. Sicher, Sie könnten zuerst den InputStream des Antwortkörpers abrufen, einige Zeilen lesen, den Typ des Körpers bestimmen und ihn dann konvertieren. Aber so einfach ist das nicht.
Desdroid
Ich bin auf der Suche nach dem besten Weg, um mit verschiedenen Rückgabetypen umzugehen. Ihre Antwort sah ziemlich vielversprechend aus, aber ich war mir nicht sicher, woher Sie den Typ kannten. Deshalb wollte ich, dass Sie das
näher
2
Ich denke, es wird nicht funktionieren, weil Deserializer eine Ausnahme auslösen und die onFailure () nicht onResponse ()
Amt87
9

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

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

Aktualisiert

Retrofit 2 ist ab sofort erhältlich und bringt einige Änderungen an der Dokumentation und Bibliothek mit sich.

Unter http://square.github.io/retrofit/#restadapter-configuration können Anforderungs- und Antworttextobjekte verwendet werden.

Justin Slade
quelle
6
Ich fürchte, ich kann den von Ihnen bereitgestellten Abschnitt nicht finden. Gibt es ein Synonym?
Zionpi
7

Ich weiß, dass ich sehr, sehr spät zur Party komme. Ich hatte ein ähnliches Problem und löste es einfach so:

public class TrackerRefResponse {

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

}

Ich habe buchstäblich nur geändert, um in Objekt zu tippen. 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), sodass die Verwendung eines Konverters das Leben erschwert hätte. Verwendet 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 auf der Suche nach einer einfachen Antwort :).

Brotdose
quelle
3

Wenn es nicht möglich wäre, die Backend-API zu ändern, würde ich die folgenden Varianten in Betracht ziehen (wenn Gson zum Konvertieren von JSON verwendet wird).

  1. Wir können Adapter vom Typ Gson verwenden , um einen benutzerdefinierten Adapter für den ResponseMessageTyp zu erstellen , der dynamisch entscheidet, wie der eingehende JSON analysiert wird (unter Verwendung von etwas wie if (reader.peek() == JsonToken.STRING)).

  2. Fügen Sie einige Metainformationen, die den Antworttyp beschreiben, in einen HTTP-Header ein und bestimmen Sie daraus, welche Typinformationen an die Gson-Instanz gesendet werden müssen.

Roman Mazur
quelle
0

Ich bin auch von diesem Problem ausgegangen. 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"
        }
    ]
}
}

Bei Fehler,

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

dafür habe ich gerade ein anderes POJO erstellt 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;
}

im obigen Fall, wenn der resultSchlüssel auf json data1, data2 enthält, wird der ValidateUserResult.javainitialisiert. Wenn ein Fehler auftritt, wird die Error.javaKlasse initialisiert.

Akhil T Mohan
quelle
0

Ich weiß, dass ich zu spät komme, aber ich möchte nur meine Gedanken teilen. Ich habe an einem Projekt gearbeitet, in dem ich eine Methode schreibe. Die Methode verwendet die Nachrüstung, um Daten vom Server abzurufen. Da andere Entwickler in meinem Unternehmen diese Methode verwenden, konnte ich keine POJOKlasse verwenden (in Ihrem Beispiel die TrackerRefResponseKlasse). Also habe ich folgendes benutzt JsonObject/ Objectmögen:

Schnittstelle APIService.java

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

Dann schrieb ich in meiner Methode Folgendes:

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
}

Sie können auch Objectanstelle von 'JsonObject' verwenden. Wenn Sie später wissen, um welche Art von Antwort es sich handelt, können Sie diese möglicherweise in das gewünschte Objekt umwandeln.

Qazi Fahim Farhan
quelle