Retrofit ব্যবহার করে GSON এর সাথে নেস্টেড JSON অবজেক্ট পান


111

আমি আমার অ্যান্ড্রয়েড অ্যাপ্লিকেশন থেকে একটি এপিআই গ্রহণ করছি এবং সমস্ত জেএসএন প্রতিক্রিয়াগুলি এর মতো:

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

তারপরে আপনার একটি Contentপজো হবে:

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

তারপরে আপনি একটি ডিসিরিয়ালাইজার লিখুন:

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

আপনি সরাসরি আপনার জেএসওনকে ডিসিজায়ালাইজ করতে পারেন Content:

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

মন্তব্য থেকে যোগ করতে সম্পাদনা করুন:

আপনার যদি বিভিন্ন ধরণের বার্তা থাকে তবে সেগুলির সবার কাছে "বিষয়বস্তু" ক্ষেত্র থাকে তবে আপনি এইটি করে ডিসিরিসালাইজারকে জেনেরিক তৈরি করতে পারেন:

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 retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

1
বাহ, দুর্দান্ত! ধন্যবাদ! : ডি সমাধানটি সাধারণকরণের কোনও উপায় আছে যাতে প্রতি প্রকার প্রতিক্রিয়া অনুযায়ী আমাকে একটি জসনডিজারাইজার তৈরি করতে হবে না?
মাইকেলার

1
এটা চমৎকার! একটি জিনিস পরিবর্তন করতে হবে: গসন গসন = নতুন গসনবিল্ডার ()। তৈরি (); গসনের পরিবর্তে জিএসন = নতুন গসনবিল্ডার ()। বিল্ড (); এর দুটি উদাহরণ রয়েছে।
নেলসন ওসাকি

7
@ ফেয়ার আপনি retrofit setConverter(new GsonConverter(gson))এর RestAdapter.Builderক্লাসে কল করতে পারেন
aky

2
@ ব্রায়ানআরচ ধন্যবাদ, সুন্দর উত্তর .. আমি কি নিবন্ধভুক্ত Person.classএবং List<Person>.class/ Person[].classপৃথক ডিসরিওলাইজারের সাথে থাকা উচিত ?
আখি

2
"স্ট্যাটাস" এবং "কারণ" পাওয়ার কোনও সম্ভাবনা? উদাহরণস্বরূপ, যদি সমস্ত অনুরোধগুলি সেগুলি ফিরিয়ে দেয়, আমরা কী তাদের একটি সুপার ক্লাসে থাকতে পারি এবং "বিষয়বস্তু" থেকে প্রকৃত POJO যা সাবক্ল্যাস ব্যবহার করতে পারি?
নিমা জি

14

@ ব্রায়ানআরচের সমাধানটি সঠিক সমাধান। এটি লক্ষণীয় যে বিশেষ ক্ষেত্রে যেখানে আপনি কাস্টম অবজেক্টগুলিকে নেস্ট করেছেন যেখানে উভয়ই একটি কাস্টম প্রয়োজন TypeAdapter, আপনার অবশ্যই জিএসএন-TypeAdapterনতুন দৃষ্টান্তের সাথে নিবন্ধন করতে হবে , অন্যথায় দ্বিতীয়টি TypeAdapterকখনও কল করা হবে না। এটি কারণ আমরা একটি নতুন তৈরি করছিGson আমাদের কাস্টম ডিসারিয়ালাইজারের ভিতরে উদাহরণ ।

উদাহরণস্বরূপ, যদি আপনার নিম্নলিখিত জসন থাকে:

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

এবং আপনি চেয়েছিলেন যে এই জেএসএনকে নিম্নলিখিত বিষয়গুলিতে ম্যাপ করা উচিত:

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

class SubContent
{
    public String baz;
}

আপনি রেজিস্টার করতে হবে SubContentএর TypeAdapter। আরও দৃ be় হতে, আপনি নিম্নলিখিতগুলি করতে পারেন:

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
@ মিঃটি এটি হবেjava.lang.reflect.Type
এডান

1
সাব কন্টেন্টডিজারাইজার ক্লাসটি কোথায়? @ কেমার্লো
লোগ্রনজে

10

বিট দেরিতে তবে আশা করি এটি কারও সাহায্য করবে।

কেবল নিম্নলিখিত টাইপএডাপ্টারফ্যাক্টরি তৈরি করুন।

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

এবং এটি আপনার জিএসএন বিল্ডারে যুক্ত করুন:

.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

অথবা

 yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());

আমি দেখতে ঠিক এটি দেখতে। কারণ আমার কাছে "ডেটা" নোডের সাথে অনেক ধরণের মোড়ক রয়েছে এবং আমি তাদের প্রত্যেকটিতে টাইপএডাপ্টার যুক্ত করতে পারি না। ধন্যবাদ!
সের্গেই আইরিসভ

@ সের্গে আইরিসভ আপনাকে স্বাগত জানাই আপনি এই উত্তরটি ভোট দিতে পারেন যাতে এটি আরও বেশি হয় :)
মতিন পেট্রলাক

একাধিক পাস কিভাবে jsonElement? মত আমার আছে content, content1ইত্যাদি
Sathish কুমার জে

আমি মনে করি আপনার ব্যাক-এন্ড ডিভগুলি কাঠামো পরিবর্তন করা উচিত এবং সামগ্রী, সামগ্রী 1 পাস করবেন না ... এই পদ্ধতির সুবিধা কী?
মতিন পেট্রোলাক

ধন্যবাদ! এটি সঠিক উত্তর। @ মেরিন পেট্রোলাক: সুবিধাটি হ'ল এই ফর্মটি পরিবর্তনের জন্য ভবিষ্যতের প্রমাণ। "সামগ্রী" হ'ল প্রতিক্রিয়া সামগ্রী। ভবিষ্যতে তারা "ভার্সন", "লাস্টআপডেটেড", "সেশনটোকেন" ইত্যাদির মতো নতুন ক্ষেত্র আসতে পারে। আপনি যদি ইতিমধ্যে আপনার প্রতিক্রিয়া সামগ্রীটি মোড়ানো না করেন তবে আপনি নতুন কাঠামোর সাথে খাপ খাইয়ে নিতে আপনার কোডে একগুচ্ছ ওয়ার্কআরাউন্ডে চলে যাবেন।
মোটিজেনফ্লো

7

কয়েক দিন আগেও একই সমস্যা ছিল। আমি রেসপন্স র‌্যাপার ক্লাস এবং আরএক্সজাভা ট্রান্সফর্মার ব্যবহার করে এটি সমাধান করেছি, যা আমি মনে করি এটি বেশ নমনীয় সমাধান:

আবরণ:

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

আরএক্স ট্রান্সফর্মার:

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

আমার বিষয়: retrofit 2 RxJava - Gson - "গ্লোবাল" deserialization, প্রতিক্রিয়ার ধরণ পরিবর্তন করুন


মাইপোজো দেখতে কেমন?
ইগরগানাপলস্কি

1
@ ইগোরগানাপলস্কি মাইপোজিও আপনি চাইলে দেখতে পারেন। এটি কোনও সার্ভার থেকে পুনরুদ্ধার করা আপনার সামগ্রীর ডেটার সাথে মেলে। এই শ্রেণীর কাঠামোটি আপনার সিরিয়ালাইজেশন রূপান্তরকারী (জিসন, ​​জ্যাকসন ইত্যাদি) এর সাথে সামঞ্জস্য করা উচিত।
রাফাকব

@ ইরাকাকব আপনি কি আমার ইস্যুতে আমাকে সাহায্য করতে পারেন? খুব সহজতম উপায়ে আমার নেস্টেড জেএসনে একটি ক্ষেত্র পাওয়ার জন্য কঠোর চেষ্টা করুন। : এখানে আমার প্রশ্ন 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

@ ব্রায়ান রোচ এবং @ ইরাকাকোবের উত্তর অনুসারে আমি নিম্নলিখিত পদ্ধতিতে এটি করেছি

সার্ভার থেকে জসন প্রতিক্রিয়া

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

এটি @ ইয়ারুলিনের মতো একই সমাধান তবে ধরুন শ্রেণীর নামটি 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);

    }
}

তারপরে উপরের নমুনা পেইলডকে বিশ্লেষণ করার জন্য, আমরা জিএসএন ডিসরিওলাইজার নিবন্ধন করতে পারি। এটি সমস্যাযুক্ত কারণ কীটি কেস সংবেদনশীল, সুতরাং শ্রেণীর নামের ক্ষেত্রে অবশ্যই জেএসএন কী এর সাথে মেলে।

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

এই ধরনের ক্ষেত্রে আমি উপরে তালিকাভুক্ত হিসাবে একই রকম সমাধান ব্যবহার করেছি তবে এটি টুইট করতে হয়েছিল। আপনি এখানে সারাংশ দেখতে পারেন । এটি এখানে সফটওয়্যারটিতে পোস্ট করা কিছুটা বড়।

টীকাটি @InnerKey("content")ব্যবহৃত হয় এবং বাকী কোডটি হ'ল জিসনের সাথে এর ব্যবহারের সুবিধার্থে।


আপনি আমার প্রশ্নটি পাশাপাশি সাহায্য করতে পারেন। : একটি এইচআরডি সময় সহজ সম্ভাব্য উপায়ে একটি নেস্টেড JSON থেকে একটি ক্ষেত্র পেয়ে আছে stackoverflow.com/questions/56501897/...

0

জিএসওএন দ্বারা জেএসওএন থেকে সর্বাধিক deserialized করা সমস্ত শ্রেণীর সদস্য এবং অভ্যন্তরীণ শ্রেণীর সদস্যদের জন্য ভুলে যাবেন না @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);
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.