रेट्रोस्टिट का उपयोग करके GSON के साथ नेस्टेड JSON ऑब्जेक्ट प्राप्त करें


111

मैं अपने Android ऐप से API का उपभोग कर रहा हूं, और JSON की सभी प्रतिक्रियाएं इस तरह हैं:

{
    'status': 'OK',
    'reason': 'Everything was fine',
    'content': {
         < some data here >
}

समस्या यह है कि मेरे सभी POJO के पास एक status, reasonफ़ील्ड हैं, और फ़ील्ड के अंदर contentवह वास्तविक POJO है जो मैं चाहता हूं।

क्या हमेशा contentक्षेत्र को निकालने के लिए ग्सन का एक कस्टम कनवर्टर बनाने का कोई तरीका है , इसलिए रेट्रोफिट ने एपीजेओओ को लौटा दिया?



मैंने दस्तावेज़ पढ़ा, लेकिन मैं यह नहीं देखता कि यह कैसे करना है ... :( मुझे नहीं पता कि मेरी समस्या को हल करने के लिए कोड कैसे प्रोग्राम किया
जाए

मुझे उत्सुकता है कि आप उन स्थिति परिणामों को संभालने के लिए सिर्फ अपने POJO वर्ग को प्रारूपित क्यों नहीं करेंगे।
जे जे

जवाबों:


168

आप एक कस्टम डेज़रलाइज़र लिखते हैं जो एम्बेडेड ऑब्जेक्ट को वापस करता है।

मान लीजिए कि आपका JSON है:

{
    "status":"OK",
    "reason":"some reason",
    "content" : 
    {
        "foo": 123,
        "bar": "some value"
    }
}

फिर आपके पास एक ContentPOJO होगा:

class Content
{
    public int foo;
    public String bar;
}

तो फिर तुम एक deserializer लिखें:

class MyDeserializer implements JsonDeserializer<Content>
{
    @Override
    public Content deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
        throws JsonParseException
    {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        return new Gson().fromJson(content, Content.class);

    }
}

अब यदि आप एक निर्माण Gsonकरते हैं GsonBuilderऔर डेजिऐलाइज़र को पंजीकृत करते हैं:

Gson gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer())
        .create();

आप अपने JSON को सीधे अपने से अलग कर सकते हैं Content:

Content c = gson.fromJson(myJson, Content.class);

टिप्पणियों से जोड़ने के लिए संपादित करें:

यदि आपके पास विभिन्न प्रकार के संदेश हैं, लेकिन उन सभी में "सामग्री" फ़ील्ड है, तो आप ऐसा करके Deserializer को सामान्य बना सकते हैं:

class MyDeserializer<T> implements JsonDeserializer<T>
{
    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
        throws JsonParseException
    {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        return new Gson().fromJson(content, type);

    }
}

आपको अपने प्रत्येक प्रकार के लिए एक उदाहरण दर्ज करना होगा:

Gson gson = 
    new GsonBuilder()
        .registerTypeAdapter(Content.class, new MyDeserializer<Content>())
        .registerTypeAdapter(DiffContent.class, new MyDeserializer<DiffContent>())
        .create();

जब आप कॉल .fromJson()करते हैं तो टाइप को डेज़राइज़र में ले जाया जाता है, इसलिए इसे तब आपके सभी प्रकारों के लिए काम करना चाहिए।

और अंत में जब एक पुराना उदाहरण बना:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

1
वाह बहुत अच्छा है! धन्यवाद! : घ समाधान को सामान्य करने का कोई तरीका है तो मुझे प्रत्येक प्रकार की प्रतिक्रिया के लिए एक JsonDeserializer बनाने की आवश्यकता नहीं है?
22

1
ये अद्भुत है! बदलने के लिए एक चीज़: Gson gson = new GsonBuilder ()। Create (); Gson gson की जगह = new GsonBuilder ()। build (); इसके दो उदाहरण हैं।
नेल्सन ओसाकी

7
@ रेफर आप setConverter(new GsonConverter(gson))रेट्रोफिट की RestAdapter.Builderक्लास में बुला सकते हैं
akhy

2
@BrianRoach धन्यवाद, अच्छा जवाब .. क्या मुझे पंजीकृत होना चाहिए Person.classऔर List<Person>.class/ Person[].classअलग हुए देशद्रोही के साथ?
अंकि

2
"स्थिति" और "कारण" पाने की कोई संभावना भी? उदाहरण के लिए यदि सभी अनुरोध उन्हें वापस कर देते हैं, तो क्या हम उन्हें सुपर क्लास में रख सकते हैं और उपवर्गों का उपयोग कर सकते हैं जो "सामग्री" से वास्तविक पीओजेओ हैं?
नीमा जी

14

@ ब्रायनरॉच का समाधान सही समाधान है। यह ध्यान देने योग्य है कि उस विशेष मामले में जहां आपने कस्टम ऑब्जेक्ट्स को नेस्टेड किया है, जिसमें दोनों को कस्टम की आवश्यकता है TypeAdapter, आपको जीएसओएनTypeAdapter के नए उदाहरण के साथ पंजीकृत होना चाहिए , अन्यथा दूसरा TypeAdapterकभी नहीं कहा जाएगा। ऐसा इसलिए है क्योंकि हम Gsonअपने कस्टम डेज़राइज़र के अंदर एक नया उदाहरण बना रहे हैं ।

उदाहरण के लिए, यदि आपके पास निम्नलिखित json था:

{
    "status": "OK",
    "reason": "some reason",
    "content": {
        "foo": 123,
        "bar": "some value",
        "subcontent": {
            "useless": "field",
            "data": {
                "baz": "values"
            }
        }
    }
}

और आप चाहते थे कि इस JSON को निम्नलिखित वस्तुओं के लिए मैप किया जाए:

class MainContent
{
    public int foo;
    public String bar;
    public SubContent subcontent;
}

class SubContent
{
    public String baz;
}

आप रजिस्टर करने की आवश्यकता होगी SubContentकी TypeAdapter। अधिक मजबूत होने के लिए, आप निम्न कार्य कर सकते हैं:

public class MyDeserializer<T> implements JsonDeserializer<T> {
    private final Class mNestedClazz;
    private final Object mNestedDeserializer;

    public MyDeserializer(Class nestedClazz, Object nestedDeserializer) {
        mNestedClazz = nestedClazz;
        mNestedDeserializer = nestedDeserializer;
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
        // Get the "content" element from the parsed JSON
        JsonElement content = je.getAsJsonObject().get("content");

        // Deserialize it. You use a new instance of Gson to avoid infinite recursion
        // to this deserializer
        GsonBuilder builder = new GsonBuilder();
        if (mNestedClazz != null && mNestedDeserializer != null) {
            builder.registerTypeAdapter(mNestedClazz, mNestedDeserializer);
        }
        return builder.create().fromJson(content, type);

    }
}

और फिर इसे ऐसे बनाएं:

MyDeserializer<Content> myDeserializer = new MyDeserializer<Content>(SubContent.class,
                    new SubContentDeserializer());
Gson gson = new GsonBuilder().registerTypeAdapter(Content.class, myDeserializer).create();

यह आसानी से घोंसले "सामग्री" मामले के लिए इस्तेमाल किया जा सकता है और MyDeserializerसाथ ही शून्य मानों के साथ एक नए उदाहरण में गुजर रहा है ।


1
"टाइप" किस पैकेज से है? एक लाख पैकेज हैं जिनमें "टाइप" वर्ग शामिल है। धन्यवाद।
काइल ब्रिडेनस्टाइन

2
@ Mr.Tea यह हो जाएगाjava.lang.reflect.Type
एडन

1
SubContentDeserializer वर्ग कहाँ है? @ केरमलो
लोगोनज डे

10

थोड़ा देर से लेकिन उम्मीद है कि यह किसी की मदद करेगा।

बस टाइप करें AdAdapterFactory निम्नलिखित।

    public class ItemTypeAdapterFactory implements TypeAdapterFactory {

      public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {

        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);

        return new TypeAdapter<T>() {

            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            public T read(JsonReader in) throws IOException {

                JsonElement jsonElement = elementAdapter.read(in);
                if (jsonElement.isJsonObject()) {
                    JsonObject jsonObject = jsonElement.getAsJsonObject();
                    if (jsonObject.has("content")) {
                        jsonElement = jsonObject.get("content");
                    }
                }

                return delegate.fromJsonTree(jsonElement);
            }
        }.nullSafe();
    }
}

और इसे अपने GSON बिल्डर में जोड़ें:

.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

या

 yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

यह ठीक वैसा है जैसा मैं देखता हूं। क्योंकि मेरे पास "डेटा" नोड के साथ लिपटे हुए बहुत सारे प्रकार हैं और मैं उनमें से प्रत्येक में टाइप एडेप्टर नहीं जोड़ सकता। धन्यवाद!
सर्गेई आइरिसोव

@SergeyIrisov आपका स्वागत है। आप इस जवाब को वोट कर सकते हैं ताकि यह अधिक हो जाए :)
मतीन पेट्रुलक

कई पास कैसे करें jsonElement? जैसे मेरे पास है content, content1आदि
सतीश कुमार जे

मुझे लगता है कि आपके बैक-एंड देवताओं को संरचना को बदलना चाहिए और सामग्री को पास नहीं करना चाहिए, content1 ... इस दृष्टिकोण का क्या फायदा है?
मतीन पेत्रुलक

धन्यवाद! यह एकदम सही जवाब है। @ मरीन पेट्रुलक: लाभ यह है कि यह फ़ॉर्म परिवर्तनों के लिए भविष्य का प्रमाण है। "सामग्री" प्रतिक्रिया सामग्री है। भविष्य में वे "वर्जन", "लास्ट यूडेड", "सेशनटॉकन" आदि जैसे नए क्षेत्र आ सकते हैं। यदि आपने अपनी प्रतिक्रिया सामग्री पहले से नहीं लपेटी है, तो आप नए कोड के अनुकूल होने के लिए अपने कोड में वर्कअराउंड का एक समूह बनाएंगे।
मुएटज़ेनफ्लो

7

कुछ दिन पहले भी यही समस्या थी। मैंने इसे प्रतिक्रिया आवरण वर्ग और RxJava ट्रांसफार्मर का उपयोग करके हल किया है, जो मुझे लगता है कि काफी लचीला समाधान है:

आवरण:

public class ApiResponse<T> {
    public String status;
    public String reason;
    public T content;
}

कस्टम अपवाद फेंकने के लिए, जब स्थिति ठीक नहीं है:

public class ApiException extends RuntimeException {
    private final String reason;

    public ApiException(String reason) {
        this.reason = reason;
    }

    public String getReason() {
        return apiError;
    }
}

Rx ट्रांसफार्मर:

protected <T> Observable.Transformer<ApiResponse<T>, T> applySchedulersAndExtractData() {
    return observable -> observable
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .map(tApiResponse -> {
                if (!tApiResponse.status.equals("OK"))
                    throw new ApiException(tApiResponse.reason);
                else
                    return tApiResponse.content;
            });
}

उदाहरण का उपयोग:

// Call definition:
@GET("/api/getMyPojo")
Observable<ApiResponse<MyPojo>> getConfig();

// Call invoke:
webservice.getMyPojo()
        .compose(applySchedulersAndExtractData())
        .subscribe(this::handleSuccess, this::handleError);


private void handleSuccess(MyPojo mypojo) {
    // handle success
}

private void handleError(Throwable t) {
    getView().showSnackbar( ((ApiException) throwable).getReason() );
}

मेरा विषय: रेट्रोफिट 2 RxJava - Gson - "Global" deserialization, change response type


MyPojo कैसा दिखता है?
इगोरगानापोलस्की

1
@IgorGanapolsky MyPojo को आप चाहे तो देख सकते हैं। यह एक सर्वर से प्राप्त आपके सामग्री डेटा से मेल खाना चाहिए। इस श्रेणी की संरचना को आपके क्रमबद्धता कनवर्टर (गॉन, जैक्सन आदि) से समायोजित किया जाना चाहिए।
रफाकोब

@ अराफकोब आप मेरे मुद्दे के साथ मेरी मदद कर सकते हैं? एक कठिन समय संभव है सबसे सरल तरीके से मेरे नेस्टेड जोंस में एक क्षेत्र प्राप्त करने का प्रयास करें। यहाँ मेरा सवाल है: stackoverflow.com/questions/56501897/…

6

ब्रायन के विचार को जारी रखना, क्योंकि हमारे पास लगभग हमेशा ही अपने स्वयं के रूट के साथ कई बाकी संसाधन हैं, यह वांछनीयता को सामान्य बनाने के लिए उपयोगी हो सकता है:

 class RestDeserializer<T> implements JsonDeserializer<T> {

    private Class<T> mClass;
    private String mKey;

    public RestDeserializer(Class<T> targetClass, String key) {
        mClass = targetClass;
        mKey = key;
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        JsonElement content = je.getAsJsonObject().get(mKey);
        return new Gson().fromJson(content, mClass);

    }
}

फिर ऊपर से नमूना पेलोड पार्स करने के लिए, हम जीएसओएन डिसेरलाइज़र को पंजीकृत कर सकते हैं:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content"))
    .build();

3

एक बेहतर समाधान यह हो सकता है ..

public class ApiResponse<T> {
    public T data;
    public String status;
    public String reason;
}

फिर, अपनी सेवा को इस तरह परिभाषित करें ।।

Observable<ApiResponse<YourClass>> updateDevice(..);

3

@Brian Roach और @rafakob के उत्तर के अनुसार मैंने निम्नलिखित तरीके से ऐसा किया

सर्वर से Json प्रतिक्रिया

{
  "status": true,
  "code": 200,
  "message": "Success",
  "data": {
    "fullname": "Rohan",
    "role": 1
  }
}

सामान्य डेटा हैंडलर वर्ग

public class ApiResponse<T> {
    @SerializedName("status")
    public boolean status;

    @SerializedName("code")
    public int code;

    @SerializedName("message")
    public String reason;

    @SerializedName("data")
    public T content;
}

कस्टम धारावाहिक

static class MyDeserializer<T> implements JsonDeserializer<T>
{
     @Override
      public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
                    throws JsonParseException
      {
          JsonElement content = je.getAsJsonObject();

          // Deserialize it. You use a new instance of Gson to avoid infinite recursion
          // to this deserializer
          return new Gson().fromJson(content, type);

      }
}

Gson ऑब्जेक्ट

Gson gson = new GsonBuilder()
                    .registerTypeAdapter(ApiResponse.class, new MyDeserializer<ApiResponse>())
                    .create();

आपी ने पुकारा

 @FormUrlEncoded
 @POST("/loginUser")
 Observable<ApiResponse<Profile>> signIn(@Field("email") String username, @Field("password") String password);

restService.signIn(username, password)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(new Observer<ApiResponse<Profile>>() {
                    @Override
                    public void onCompleted() {
                        Log.i("login", "On complete");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i("login", e.toString());
                    }

                    @Override
                    public void onNext(ApiResponse<Profile> response) {
                         Profile profile= response.content;
                         Log.i("login", profile.getFullname());
                    }
                });

2

यह @ayarulin के समान ही समाधान है लेकिन मान लें कि कक्षा का नाम JSON कुंजी नाम है। इस तरह से आपको केवल कक्षा का नाम पास करना होगा।

 class RestDeserializer<T> implements JsonDeserializer<T> {

    private Class<T> mClass;
    private String mKey;

    public RestDeserializer(Class<T> targetClass) {
        mClass = targetClass;
        mKey = mClass.getSimpleName();
    }

    @Override
    public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
            throws JsonParseException {
        JsonElement content = je.getAsJsonObject().get(mKey);
        return new Gson().fromJson(content, mClass);

    }
}

फिर ऊपर से नमूना पेलोड को पार्स करने के लिए, हम जीएसओएन डिसेरलाइज़र को पंजीकृत कर सकते हैं। यह समस्याग्रस्त है क्योंकि कुंजी केस संवेदी है, इसलिए क्लास नाम के मामले को JSON कुंजी के मामले से मेल खाना चाहिए।

Gson gson = new GsonBuilder()
.registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class))
.build();

2

यहां ब्रायन रोच और अयरुलिन के उत्तरों के आधार पर एक कोटलिन संस्करण है।

class RestDeserializer<T>(targetClass: Class<T>, key: String?) : JsonDeserializer<T> {
    val targetClass = targetClass
    val key = key

    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): T {
        val data = json!!.asJsonObject.get(key ?: "")

        return Gson().fromJson(data, targetClass)
    }
}

1

मेरे मामले में, प्रत्येक प्रतिक्रिया के लिए "सामग्री" कुंजी बदल जाएगी। उदाहरण:

// Root is hotel
{
  status : "ok",
  statusCode : 200,
  hotels : [{
    name : "Taj Palace",
    location : {
      lat : 12
      lng : 77
    }

  }, {
    name : "Plaza", 
    location : {
      lat : 12
      lng : 77
    }
  }]
}

//Root is city

{
  status : "ok",
  statusCode : 200,
  city : {
    name : "Vegas",
    location : {
      lat : 12
      lng : 77
    }
}

ऐसे मामलों में मैंने ऊपर सूचीबद्ध के रूप में एक समान समाधान का उपयोग किया था लेकिन इसे ट्विक करना था। आप यहाँ देख सकते हैं । यह SOF पर यहां पोस्ट करने के लिए बहुत बड़ा है।

एनोटेशन @InnerKey("content")का उपयोग किया जाता है और शेष कोड को Gson के साथ उपयोग करने की सुविधा प्रदान करता है।


क्या आप मेरे प्रश्न के साथ मदद कर सकते हैं। एक संभव समय में एक

0

सभी कक्षा के सदस्यों और इनर क्लास के सदस्यों के लिए मत भूलना @SerializedNameऔर @Exposeएनोटेशन करें जो कि जीएसओएन द्वारा जीएसओएन से सर्वाधिक वांछित हैं।

Https://stackoverflow.com/a/40239512/1676736 पर देखें


0

एक और सरल उपाय:

JsonObject parsed = (JsonObject) new JsonParser().parse(jsonString);
Content content = gson.fromJson(parsed.get("content"), Content.class);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.