Retrofit में डायनामिक JSON को कैसे संभालें?


82

मैं रेट्रोफिट कुशल नेटवर्किंग लाइब्रेरी का उपयोग कर रहा हूं, लेकिन मैं गतिशील JSON को संभालने में असमर्थ हूं जिसमें एकल उपसर्ग है responseMessageजो objectयादृच्छिक रूप से बदलता है , responseMessageकुछ मामलों में (गतिशील रूप से) एक ही उपसर्ग ( ) स्ट्रिंग में बदलता है।

Json प्रारूप प्रतिक्रिया की वस्तु

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

}

responseMessage JSON प्रारूप गतिशील रूप से स्ट्रिंग में टाइप करने के लिए बदलता है:

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

मेरी समस्या तब से है जब रेट्रोफ़िट में बिल्ट-इन JSONपार्सिंग है, मुझे अनुरोध के अनुसार एकल पीओजेओ आवंटित करना है! लेकिन दुर्भाग्य से एपीआई गतिशील JSONप्रतिक्रियाओं पर बनाया गया है । उपसर्ग स्ट्रिंग से ऑब्जेक्ट में क्रमिक रूप से सफलता (...) और विफलता (...) विधियों में बदल जाएगा!

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


}

ऊपर दिए गए कोड में POJO TrackerRefResponse.java उपसर्ग responseMessage स्ट्रिंग या ऑब्जेक्ट प्रकार की responseMessage पर सेट है, इसलिए हम POJO को उसी नाम (जावा मूल बातें :)) के साथ रेफ चर के साथ बना सकते हैं, इसलिए JSONरेट्रोफिट में गतिशील के लिए एक ही समाधान की तलाश कर रहे हैं । मुझे पता है कि यह async कार्य के साथ सामान्य http क्लाइंट में बहुत आसान काम है, लेकिन यह REST-Api JSONपार्सिंग में सबसे अच्छा अभ्यास नहीं है ! प्रदर्शन को देखते हुए बेंचमार्क हमेशा वॉली या रेट्रोफिट सबसे अच्छा विकल्प है, लेकिन मैं गतिशील संभाल नहीं रहा हूँ JSON!

संभव समाधान मुझे पता है

  1. Http क्लाइंट पार्सिंग के साथ पुराने एसाइक कार्य का उपयोग करें। :(

  2. Restapi बैकएंड डेवलपर को मनाने की कोशिश करें।

  3. कस्टम रेट्रोफ़िट क्लाइंट बनाएं :)


1
"Restapi बैकएंड डेवलपर को समझाने की कोशिश करें।" मेरे लिए चाल चली! जबरदस्त हंसी! ;) (nb: मैं बैकेंड देव भी था, मुझे खुद को समझाने के लिए!)
mrx

जवाबों:


38

पार्टी के लिए देर से, लेकिन आप एक कनवर्टर का उपयोग कर सकते हैं।

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

फिर आप एक स्थिर वर्ग का उपयोग करते हैं जो रेट्रोफिट के कनवर्टर को लागू करता है:

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

मैंने इस नमूना कनवर्टर को लिखा है, इसलिए यह स्ट्रिंग, ऑब्जेक्ट, JsonObject या मैप <स्ट्रिंग, ऑब्जेक्ट> के रूप में यासन प्रतिक्रिया देता है। स्पष्ट रूप से सभी रिटर्न प्रकार हर Json के लिए काम नहीं करेंगे, और सुधार के लिए निश्चित जगह है। लेकिन यह दर्शाता है कि डायनामिक जोंस के लगभग किसी भी प्रतिक्रिया को बदलने के लिए कन्वर्टर का उपयोग कैसे किया जाता है।


13
RestAdapterइस उदाहरण को देखना रेट्रोफिट के लिए है 1. आप रेट्रोफिट 2 में उसी कन्वर्टर को कैसे लागू करेंगे?
androidtitan

1
कनवर्ज़न
एक्ससेप्शन

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" विधि लागू करनी चाहिए।


मैं रेट्रोफिट 2 में कैसे कर सकता हूं?
वादिम कोटोव

1
"CheckResponseMessage" क्या है?
शशांक सिंह

15

gson-converterनीचे के रूप में उपयोग करके कस्टम deserialisation का प्रयास करें (Retrofit 2.0 के लिए अद्यतन उत्तर)

नीचे दिखाए गए अनुसार तीन मॉडल बनाएं

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 (कस्टम 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);
        }

}
}

रेट्रोफिट 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) {
        }
    });

लाइब्रेरी का इस्तेमाल किया

संकलन 'com.squareup.retrofit2: रेट्रोफिट: 2.3.0'

संकलन 'com.squareup.retrofit2: कन्वर्टर-ग्सन: 2.3.0'

***** पिछला उत्तर (उपर्युक्त उत्तर अधिक अनुशंसित है) *****

अपने पोजो को इस तरह बदलें

public class TrackerRefResponse {

  private String applicationType;
  private Object responseMessage;

  public Object getResponseMessage() {
  return responseMessage;
  }

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

और इस तरह से रेट्रोफ्रिट के ऑनरस्पॉन्स को बदलें

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

डायनामिक जोंस पार्सिंग के बारे में अधिक जानकारी के लिए आप इस पोस्ट को भी देख सकते हैं


क्या ResponseWrapper वर्ग वास्तव में आवश्यक है? मुझे लगता है कि यह बहुत भ्रामक लगता है। मैं कुछ भी पर एक कनवर्टर की जरूरत है, लेकिन पदानुक्रम में उच्चतम वस्तु ...
RabbitBones22

1
आप आवरण वर्ग अपनी भ्रमित करता है, तो बचने और इस कोशिश कर सकते हैं freshbytelabs.com/2018/12/...
नवनीत कृष्णा

9

स्वीकृत उत्तर मेरे लिए जटिल लग रहा था, मैं इसे इस तरह हल करता हूं:

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

यह आपको दिखाने के प्रयास में सिर्फ एक उदाहरण है कि आप विभिन्न मॉडल का उपयोग कैसे कर सकते हैं।

isEmailआपकी मॉडल उपयुक्त मॉडल का उपयोग करने के लिए चर सिर्फ एक बूलियन है।


क्या आप कृपया विस्तार से बता सकते हैं? यह कोड गैर वर्णनात्मक है। MType कहाँ से आता है?
Desdroid

@Desdroid मैंने कोड को सरल किया और फिर स्पष्टीकरण के साथ विस्तार किया
मेडा

2
फिर भी, मुझे विश्वास है कि यह मदद नहीं करेगा यदि आप कॉल करने से पहले प्रतिक्रिया प्रकार नहीं जानते हैं, जो कि सवाल में है। सुनिश्चित करें कि आप पहले रिस्पांस बॉडी का इनपुटस्ट्रीम प्राप्त कर सकते हैं, कुछ पंक्तियों को पढ़ सकते हैं, यह निर्धारित कर सकते हैं कि टाइप द बॉडी क्या है और फिर इसे रूपांतरित करें। लेकिन यह उतना सरल नहीं है।
Desdroid

मैं विभिन्न प्रकार के रिटर्न को संभालने के लिए सबसे अच्छे तरीके की तलाश में हूं। आपका उत्तर बहुत ही आशाजनक लग रहा था, लेकिन मुझे यकीन नहीं था कि आप टाइप कहां से जानते थे। इसीलिए मैं चाहता था कि आप इसे विस्तार से
बताएं

2
मुझे लगता है कि यह काम नहीं करेगा क्योंकि Deserializer एक अपवाद फेंक देगा और onFailure () onResponse ()
Amt87

9

आपका कोई भी संभव समाधान काम करेगा। आप जो भी कर सकते हैं वह प्रतिक्रिया के लिए रेट्रोफिट एपीआई इंटरफेस रिटर्न प्रकार भेजें। उस प्रतिक्रिया से आपको एक बॉडी मिलती है Inputstreamजिसे आप JSON ऑब्जेक्ट में बदल सकते हैं और जैसा कि आप फिट देखते हैं, पढ़ सकते हैं।

यहां देखें: http://square.github.io/retrofit/#api-declaration - RESPONSE OBJECT TYPE के तहत

अद्यतन

रेट्रोफिट 2 अभी बाहर है और इसके साथ प्रलेखन और पुस्तकालय में कुछ बदलाव हुए हैं।

Http://square.github.io/retrofit/#restadcape-configuration को देखें अनुरोध और प्रतिक्रिया बॉडी ऑब्जेक्ट हैं जिनका उपयोग किया जा सकता है।


6
मुझे डर है कि मैं आपके द्वारा प्रदान किए गए अनुभाग को नहीं पा सकता हूं, क्या कोई पर्यायवाची हैं?
zionpi

7

मुझे पता है कि मैं पार्टी में बहुत देर से आता हूं। मेरे पास एक समान मुद्दा था और बस इसे इस तरह हल किया:

public class TrackerRefResponse {

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

}

मैं सचमुच सिर्फ ऑब्जेक्ट के लिए टाइप करने के लिए बदल गया। मैंने इस दृष्टिकोण को चुना क्योंकि प्रतिक्रिया में केवल एक क्षेत्र गतिशील था (मेरे लिए, मेरी प्रतिक्रिया अधिक जटिल थी), इसलिए एक कनवर्टर का उपयोग करने से जीवन मुश्किल हो जाता। वहाँ से वस्तु के साथ काम करने के लिए Gson का उपयोग किया जाता है, इस पर निर्भर करता है कि क्या यह एक स्ट्रिंग या ऐरे मूल्य था।

आशा है कि यह किसी को सरल उत्तर की तलाश में मदद करता है :)।


3

यदि बैकएंड एपीआई को बदलना संभव नहीं था, तो मैं निम्नलिखित वेरिएंट पर विचार करूंगा (यदि GSON का उपयोग JSON कन्वर्ट करने के लिए किया जाता है)।

  1. हम प्रकार के लिए एक कस्टम एडेप्टर बनाने के लिए Gson प्रकार एडेप्टर का उपयोग कर सकते हैं ResponseMessageजो गतिशील रूप से निर्णय लेता है कि कैसे इनिंग JSON को पार्स किया जाए (कुछ का उपयोग करके if (reader.peek() == JsonToken.STRING))।

  2. प्रतिक्रिया प्रकार का एक HTTP शीर्ष लेख में वर्णन करने के लिए कुछ मेटा जानकारी डालें और इसका उपयोग यह निर्धारित करने के लिए करें कि किस प्रकार की जानकारी को Gson उदाहरण के लिए खिलाया जाना चाहिए।


1

इसके अलावा आपने जो बताया -

कॉलबैक का उपयोग करें तब आप नियमित प्राप्त विधि का उपयोग करके फ़ील्ड को पुनः प्राप्त कर सकते हैं। अधिक जानकारी के लिए, gson के javadoc के माध्यम से जाना।

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


1

मुझे पता है कि मुझे देर हो गई है, लेकिन मैं सिर्फ अपने विचार साझा करना चाहता हूं। मैं एक परियोजना पर काम कर रहा था, जहां मैं एक विधि लिख रहा हूं। सर्वर से डेटा प्राप्त करने के लिए विधि रेट्रोफिट का उपयोग करती है। चूंकि मेरी कंपनी के अन्य डेवलपर्स इस पद्धति का उपयोग करेंगे, इसलिए मैं एक POJOवर्ग (आपके उदाहरण में, TrackerRefResponseकक्षा) का उपयोग नहीं कर सका । तो मैंने इसका उपयोग किया JsonObject/ Objectजैसे:

इंटरफ़ेस APIService.java

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

तब मेरी विधि में, मैंने यह लिखा:

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
}
        

यदि आप जानते हैं कि एक निश्चित अटिब्यूट आपको बताएगा कि यह किस प्रकार की प्रतिक्रिया है, तो आप JsonObject का उपयोग कर सकते हैं, उस विशेषता को पढ़ सकते हैं और फिर मॉडल को इस तरह से डाल सकते हैं:

// ... 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` के स्थान पर उपयोग कर सकते हैं । बाद में, जब आपको पता चलेगा कि यह किस तरह की प्रतिक्रिया है, तो आप इसे वांछित वस्तु में डाल सकते हैं।


यह समाधान मेरी समस्या को बेहतर तरीके से फिट करता है। यदि सब कुछ ठीक है, तो नल के साथ एपि प्रतिक्रिया में से एक है, और जब समस्या होती है, तो साधारण जोंस ऑब्जेक्ट में त्रुटि संदेश के साथ सफलता (कोड 200) वापस करें। मैं JsonNull को डिफ़ॉल्ट प्रतिक्रिया के रूप में उपयोग करता हूं।
यज़ीदीफ़

0

मैं भी इस मुद्दे पर भाग गया। लेकिन मुझे यकीन नहीं है कि यह आपका मामला था, (मैं Retrofit2 का उपयोग कर रहा हूं)

मेरे मामले में मुझे त्रुटि और सफलता संदेशों को संभालने की आवश्यकता है।

सफलता पर

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

त्रुटि पर,

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

इसके लिए मैंने सिर्फ एक और 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;
}

उपरोक्त मामले में अगर resultjson पर कुंजी में data1, data2 है तो ValidateUserResult.javaआरंभ करें। यदि त्रुटि होती है तो Error.javaकक्षा आरंभिक हो जाती है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.