Retrofit'te Dynamic JSON ile nasıl başa çıkılır?


82

Güçlendirme verimli ağ kitaplığını kullanıyorum, ancak rastgele olarak responseMessagedeğişen tek bir önek içeren Dinamik JSON'u objectişleyemiyorum, aynı önek ( responseMessage) bazı durumlarda (dinamik olarak) Dize olarak değişir.

Json formatı ResponseMessage nesnesi:

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

}

responseMessage Json formatı, tür dizesine dinamik olarak değişir:

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

Benim sorunum, güçlendirme yerleşik JSONayrıştırmaya sahip olduğundan, istek başına tek POJO atamalıyım! ancak REST-API maalesef dinamik JSONyanıtlar üzerine inşa edilmiştir . Önek hem rastgele nesneye dizesinden değişecek başarı (...) ve başarısızlık (...) yöntemlerine!

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


}

Yukarıdaki kodda POJO TrackerRefResponse.java önek responseMessage, responseMessage türünde dizeye veya nesneye ayarlanmıştır, böylece POJO'yu aynı ada sahip ref değişkeniyle (java basics :)) oluşturabiliriz, bu yüzden JSONRetrofit'te dinamik için aynı çözümü arıyorum . Zaman uyumsuz görevli normal http istemcilerinde bunun çok kolay bir iş olduğunu biliyorum, ancak REST-Api JSONçözümlemesindeki en iyi uygulama bu değil ! performans Kıyaslamalarına bakmak her zaman Volley veya Retrofit en iyi seçimdir, ancak dinamikle başa çıkmada başarısız oldum JSON!

Bildiğim olası çözüm

  1. Http istemcisi ayrıştırması ile eski asyc görevini kullanın. :(

  2. RESTapi arka uç geliştiricisini ikna etmeye çalışın.

  3. Özel Retrofit istemcisi oluşturun :)


1
"RESTapi arka uç geliştiricisini ikna etmeye çalışın." benim için hile yaptı! haha! ;) (nb: Ben de arka uç geliştiriciydim, kendimi ikna
etmeliyim

Yanıtlar:


38

Partiye geç kalıyorsunuz ama dönüştürücü kullanabilirsiniz.

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

Sonra retrofit'in Dönüştürücüsünü uygulayan statik bir sınıf kullanırsınız:

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

Bu örnek dönüştürücüyü yazdım, böylece Json yanıtını String, Object, JsonObject veya Map <String, Object> olarak döndürür. Açıkçası, tüm iade türleri her Json için işe yaramayacak ve iyileştirme için kesinlikle yer var. Ancak neredeyse her yanıtı dinamik Json'a dönüştürmek için bir Dönüştürücünün nasıl kullanılacağını gösterir.


13
RestAdapterBu örneği görmek Retrofit 1 içindir. Aynı dönüştürücüyü Retrofit 2'de nasıl uygularsınız?
androidtitan

1
ConversionException Retrofit 2'de mevcut değil :(
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);
}

"CheckResponseMessage" yöntemini uygulamalısınız.


Retrofit 2 için aynısını nasıl yapabilirim?
Vadim Kotov

1
"CheckResponseMessage" nedir?
Shashank singh

15

Aşağıdaki gibi özel seriden ayırmayı deneyin gson-converter(Retrofit 2.0 için güncellenmiş yanıt)

Aşağıda gösterildiği gibi üç model oluşturun

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 (özel seri çözümleyici )

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 Uygulaması

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

Kullanılan Kitaplıklar

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

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

***** Önceki Cevap (yukarıdaki cevap daha çok tavsiye edilir) *****

Pojo'nuzu böyle değiştirin

public class TrackerRefResponse {

  private String applicationType;
  private Object responseMessage;

  public Object getResponseMessage() {
  return responseMessage;
  }

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

ve güçlendirmenin onResponse'unu şu şekilde değiştirin

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

Ayrıca kontrol edebilir dinamik json ayrıştırma hakkında daha fazla bilgi için bu yazı


ResponseWrapper sınıfı gerçekten gerekli mi? Bence çok kafa karıştırıcı görünüyor. Hiyerarşideki en yüksek nesne dışında herhangi bir şey için bir dönüştürücüye ihtiyacım var ...
RabbitBones22

1
Eğer kafa karıştırıcıysa sarmalayıcı sınıfından kaçınabilir ve bu freshbytelabs.com/2018/12/…
Navneet Krishna

9

Kabul edilen cevap benim için çok karmaşık göründü, şu şekilde çözüyorum:

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

Bu, size farklı modeli nasıl kullanabileceğinizi gösterme girişiminde sadece bir örnektir.

Değişken isEmail, koşulunuzun uygun modeli kullanması için yalnızca bir mantıksaldır.


Lütfen bunu detaylandırır mısınız? Bu kod açıklayıcı değildir. MType nereden geliyor?
Desdroid

@Desdroid Kodu basitleştirdim ve ardından açıklamayla genişlettim
meda

2
Yine de, aramayı yapmadan önce yanıt türünü bilmiyorsanız bunun yardımcı olmayacağına inanıyorum. Elbette önce Yanıt Gövdesinin Giriş Akışını alabilir, birkaç satırı okuyabilir, Gövdenin hangi Tip olduğunu belirleyebilir ve sonra onu dönüştürebilirsiniz. Ama bu kadar basit değil.
Desdroid

Farklı iade türleriyle başa çıkmanın en iyi yolunu arıyorum. Cevabınız oldukça ümit verici görünüyordu, ancak Türü nereden bildiğinizden emin değildim. Bu yüzden bunu detaylandırmanızı istedim;)
Desdroid

2
Sanırım işe yaramayacak çünkü Deserializer bir istisna atacak ve onFailure () not
onResponse

9

Olası çözümlerinden herhangi biri işe yarayacaktır. Ayrıca yapabileceğiniz şey Retrofit api arabirimi dönüş türünü yanıta göndermektir. Bu yanıtla Inputstream, bir JSON Nesnesine dönüştürebileceğiniz ve uygun gördüğünüz şekilde okuyabileceğiniz bir gövde elde edersiniz.

Bakın: http://square.github.io/retrofit/#api-declaration - YANIT NESNE TÜRÜ altında

Güncellenmiş

Retrofit 2 şimdi çıktı ve bununla birlikte dokümantasyon ve kitaplıkta bazı değişiklikler yapıldı.

Bakmak Http://square.github.io/retrofit/#restadapter-configuration adresine kullanılabilecek istek ve yanıt gövde nesnesi vardır.


6
Korkarım verdiğiniz bölümü bulamıyorum, eş anlamlı var mı?
zionpi

7

Partiye çok geç kaldığımı biliyorum. Benzer bir sorun yaşadım ve şu şekilde çözdüm:

public class TrackerRefResponse {

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

}

Kelimenin tam anlamıyla Object olarak yazmaya geçtim. Bu yaklaşımı seçtim çünkü yanıttaki yalnızca bir alan dinamikti (benim için yanıtım çok daha karmaşıktı), bu nedenle dönüştürücü kullanmak hayatı zorlaştırırdı. Bir String veya Array değeri olup olmadığına bağlı olarak, buradan Object ile çalışmak için Gson'u kullandı.

Umarım bu, basit bir cevap arayan birine yardımcı olur :).


3

Arka uç API'sini değiştirmek mümkün olmasaydı, aşağıdaki varyantları dikkate alırdım (JSON'u dönüştürmek için Gson kullanılıyorsa).

  1. Inoming JSON'un nasıl ayrıştırılacağına (benzer bir şey kullanarak ) dinamik olarak karar veren tür için özel bir bağdaştırıcı oluşturmak için Gson türü bağdaştırıcıları kullanabiliriz .ResponseMessageif (reader.peek() == JsonToken.STRING)

  2. Yanıt türünü açıklayan bazı meta bilgileri bir HTTP başlığına koyun ve Gson örneğine hangi tür bilgilerinin beslenmesi gerektiğini belirlemek için kullanın.



1

Geç kaldığımı biliyorum ama sadece düşüncemi paylaşmak istiyorum. Yöntem yazdığım bir proje üzerinde çalışıyordum. Yöntem, sunucudan veri almak için güçlendirmeyi kullanır. Şirketimdeki diğer geliştiriciler bu yöntemi kullanacağından bir POJOsınıf kullanamadım (örneğinizde,TrackerRefResponse sınıf) . Ben de şunu kullandım JsonObject/ Objectbeğendim:

arabirim APIService.java

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

Sonra yöntemimde şunu yazdım:

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
}
        

Belirli bir niteliğin size ne tür bir yanıt olduğunu söyleyeceğini biliyorsanız, JsonObject'i kullanabilir, bu niteliği okuyabilir ve ardından modeli şu şekilde çevirebilirsiniz:

// ... 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

Object"JsonObject" yerine de kullanabilirsiniz . Daha sonra, ne tür bir yanıt olduğunu bildiğiniz zaman, belki bunu istediğiniz nesneye dönüştürebilirsiniz.


Bu çözüm benim sorunuma daha iyi uyuyor. Her şey yolundaysa null değerine sahip api yanıtlarından biri ve sorun olduğunda basit Json nesnesinde hata mesajı ile başarı (kod 200) döndürür. Varsayılan yanıt olarak JsonNull kullanıyorum.
YazidEF

0

Ben de bu sorunu çözdüm. ancak durumun bu olup olmadığından emin değilim (Retrofit2 kullanıyorum)

benim durumumda hata ve başarı mesajlarını işlemem gerekiyor.

Başarı Üzerine

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

Hata Olduğunda,

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

bunun için başka bir POJO oluşturdum 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;
}

yukarıdaki durumda result, json üzerindeki anahtar data1, data2 içeriyorsa, o ValidateUserResult.javazaman başlatılır. hata olursa Error.javasınıf başlatılır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.