Używam wydajnej biblioteki sieciowej do modernizacji, ale nie mogę obsłużyć dynamicznego formatu JSON, który zawiera pojedynczy prefiks, responseMessage
który zmienia się object
losowo, ten sam prefiks ( responseMessage
) zmienia się w niektórych przypadkach (dynamicznie).
Format Json Obiekt odpowiedzi Wiadomość:
{
"applicationType":"1",
"responseMessage":{
"surname":"Jhon",
"forename":" taylor",
"dob":"17081990",
"refNo":"3394909238490F",
"result":"Received"
}
}
responseMessage
Format JSON dynamicznie zmienia się na ciąg typu:
{
"applicationType":"4",
"responseMessage":"Success"
}
Mój problem polega na tym, że ponieważ retrofit ma wbudowane JSON
parsowanie, muszę przypisać pojedyncze POJO na żądanie! ale REST-API jest niestety zbudowane na dynamicznych JSON
odpowiedziach. Prefiks zmieni się losowo z ciągu na obiekt w obu metodach sukcesu (...) i niepowodzenia (...) !
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
}
W powyższym kodzie POJO TrackerRefResponse.java prefiks responseMessage jest ustawiony na string lub obiekt typu responseMessage, więc możemy stworzyć POJO ze zmienną ref o tej samej nazwie (podstawy java :)), więc szukam tego samego rozwiązania dla dynamiki JSON
w Retrofit. Wiem, że jest to bardzo łatwa praca w przypadku zwykłych klientów http z zadaniem asynchronicznym, ale nie jest to najlepsza praktyka podczas JSON
analizowania REST-Api ! patrząc na wydajność Benchmarki zawsze Volley lub Retrofit to najlepszy wybór, ale nie udało mi się poradzić sobie z dynamiką JSON
!
Możliwe rozwiązanie, które znam
Użyj starego zadania asyc z parsowaniem klienta http. :(
Spróbuj przekonać programistę backendu RESTapi.
Utwórz niestandardowego klienta Retrofit :)
Odpowiedzi:
Spóźniony na imprezę, ale możesz użyć konwertera.
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);
Następnie używasz klasy statycznej, która implementuje Converter retrofitu:
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(); } }
Napisałem ten przykładowy konwerter, więc zwraca on odpowiedź Json jako String, Object, JsonObject lub Map <String, Object>. Oczywiście nie wszystkie typy zwrotów będą działać dla każdego Json i jest pewne miejsce na ulepszenia. Ale pokazuje, jak używać konwertera do konwersji prawie każdej odpowiedzi na dynamiczną Json.
źródło
RestAdapter
ten przykład jest dla Retrofit 1. W jaki sposób można wdrożyć ten sam przetwornik w Retrofit 2?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); }
Musisz zaimplementować metodę „checkResponseMessage”.
źródło
Spróbuj niestandardowej deserializacji, używając
gson-converter
jak poniżej (zaktualizowana odpowiedź na Retrofit 2.0)Utwórz trzy modele, jak pokazano poniżej
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 (niestandardowy deserializator)
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); } } }
Wdrożenie 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) { } });
Używane biblioteki
***** Poprzednia odpowiedź (powyższa odpowiedź jest bardziej zalecana) *****
Zmień swoje pojo w ten sposób
public class TrackerRefResponse { private String applicationType; private Object responseMessage; public Object getResponseMessage() { return responseMessage; } public void setResponseMessage(Object responseMessage) { this.responseMessage = responseMessage; } }
i zmień w ten sposób onResponse
@Override public void onResponse(Response<TrackerRefResponse > response) { if (response.isSuccess()) { if (response.getResponseMessage() instanceof String) { handleStringResponse(); } else { handleObjectResponse(); } } }
możesz również sprawdzić ten post, aby uzyskać więcej informacji na temat dynamicznego analizowania json
źródło
Przyjęta odpowiedź wydawała mi się zbyt skomplikowana, rozwiązuję ją w ten sposób:
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()); } });
To tylko przykład, który ma pokazać, jak można używać innego modelu.
Zmienna
isEmail
jest po prostu wartością logiczną dla twojego warunku, aby użyć odpowiedniego modelu.źródło
Każde z możliwych rozwiązań zadziała. Możesz również wysłać typ zwrotu interfejsu Retrofit api do odpowiedzi. Dzięki tej odpowiedzi otrzymasz treść,
Inputstream
którą możesz przekonwertować na obiekt JSON i odczytać według własnego uznania.Spójrz na: http://square.github.io/retrofit/#api-declaration - w sekcji TYP OBIEKTU ODPOWIEDZI
Zaktualizowano
Retrofit 2 jest już dostępny, a wraz z nim pewne zmiany w dokumentacji i bibliotece.
Spójrz na http://square.github.io/retrofit/#restadapter-configuration, gdzie można użyć obiektu żądania i odpowiedzi.
źródło
Wiem, że jestem bardzo spóźniona na imprezę. Miałem podobny problem i po prostu rozwiązałem go w ten sposób:
public class TrackerRefResponse { private String applicationType; // Changed to Object. Works fine with String and array responses. private Object responseMessage; }
Dosłownie właśnie zmieniłem tekst na Object. Wybrałem to podejście, ponieważ tylko jedno pole odpowiedzi było dynamiczne (dla mnie moja odpowiedź była o wiele bardziej skomplikowana), więc użycie konwertera utrudniłoby życie. Użył Gson do pracy z Object stamtąd, w zależności od tego, czy była to wartość String, czy Array.
Mam nadzieję, że pomoże to komuś, kto szuka prostej odpowiedzi :).
źródło
Gdyby zmiana interfejsu API zaplecza nie była możliwa, rozważałbym następujące warianty (jeśli Gson jest używany do konwersji JSON).
Możemy użyć adapterów typu Gson, aby stworzyć niestandardowy adapter dla
ResponseMessage
typu, który dynamicznie decyduje, jak przeanalizować przychodzące JSON (używając czegoś podobnegoif (reader.peek() == JsonToken.STRING)
).Umieść metadane opisujące typ odpowiedzi w nagłówku HTTP i użyj ich, aby określić, jakie informacje o typie muszą zostać przekazane do instancji Gson.
źródło
Oprócz tego, co powiedziałeś -
Użyj wywołania zwrotnego Następnie możesz pobrać pola za pomocą zwykłej metody get. Aby uzyskać więcej informacji, zobacz javadoc gson.
http://google-gson.googlecode.com/svn/tags/1.2.3/docs/javadocs/com/google/gson/JsonObject.html
źródło
Wiem, że się spóźniłem, ale chcę tylko podzielić się swoją myślą. Pracowałem nad projektem, w którym piszę metodę. Metoda wykorzystuje retrofit do pobierania danych z serwera. Ponieważ inni programiści w mojej firmie będą używać tej metody, nie mogłem użyć
POJO
klasy (w Twoim przykładzie jest toTrackerRefResponse
klasa). Więc użyłemJsonObject
/Object
tak:interfejs APIService.java
public class APIService{ @FormUrlEncoded @POST Call<JsonObject> myPostMethod(@Url String url, @Field("input") String input); }
Następnie w mojej metodzie napisałem to:
Model1 model1 = null; Model2 model2 = null; 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(); // then do your stuff. maybe something like this try{ model1 = new Gson().fromJson(jsonObject, Model1.class); }catch(Exception x){} try{ model2 = new Gson().fromJson(jsonObject, Model2.class); }catch(Exception x){} if(model1 != null) { /*handle model1 */} if(model2 != null) { /*handle model2*/} // rest of the code }
Jeśli wiesz, że określony atrybut powie Ci, jaki to typ odpowiedzi, możesz użyć JsonObject, przeczytać ten atrybut, a następnie rzucić model w następujący sposób:
// ... retrofit codes @Override public void onResponse(Call<JsonObject> call, Response<JsonObject> response) { int number = jsonObject.get("applicationType").getAsInt(); if(number == 1) { model1 = new Gson().fromJson(jsonObject, Model1.class); } } // ... rest of the code
Możesz także użyć
Object
zamiast „JsonObject”. Później, kiedy będziesz wiedział, jakiego rodzaju jest to odpowiedź, być może możesz rzucić to na pożądany obiekt.źródło
Ja też prowadziłem ten problem. ale nie jestem pewien, czy to był twój przypadek (używam Retrofit2)
w moim przypadku muszę obsłużyć komunikaty o błędach i sukcesach.
Sukces
{ "call_id": 1, "status": "SUCCESS", "status_code": "SUCCESS", "result": { "data1": { "id": "RFP2UjW7p8ggpMXzYO9tRg==", "name": "abcdef", "mobile_no": "96655222", "email": "" }, "data2": [ { "no": "12345" }, { "no": "45632" } ] } }
W przypadku błędu,
{ "call_id": 1, "status": "FAILED", "status_code": "NO_RECORDS", "error": { "error_title": "xxx", "error_message": "details not found" } }
do tego właśnie stworzyłem kolejne 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; }
w powyższym przypadku, jeśli
result
klucz w json zawiera dane1, dane2, wówczasValidateUserResult.java
inicjalizacja get. jeśli błąd toError.java
inicjalizacja klasy.źródło