Получите вложенный объект JSON с помощью GSON с помощью модифицированной

Я использую API из моего приложения для Android, и все ответы JSON выглядят следующим образом:

{ 'status': 'OK', 'reason': 'Everything was fine', 'content': {  } 

Проблема в том, что все мои POJO имеют status , поля reason и внутри поля content – это реальное POJO, которое я хочу.

Есть ли способ создать пользовательский конвертер Gson, чтобы всегда извлекать поле content , поэтому переоснащение возвращает подходящий POJO?

Вы должны написать собственный десериализатор, который возвращает внедренный объект.

Скажем, ваш JSON:

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

Тогда у вас будет Content POJO:

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

Затем вы пишете десериализатор:

 class MyDeserializer implements JsonDeserializer { @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); 

Изменить для добавления комментариев:

Если у вас есть разные типы сообщений, но у всех есть поле «контент», вы можете создать общий десериализатор, выполнив следующие действия:

 class MyDeserializer implements JsonDeserializer { @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()) .registerTypeAdapter(DiffContent.class, new MyDeserializer()) .create(); 

Когда вы вызываете .fromJson() тип переносится в десериализатор, поэтому он должен работать для всех ваших типов.

И, наконец, при создании экземпляра Retrofit:

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

Решение BrianRoach является правильным решением. Стоит отметить, что в специальном случае, когда у вас есть вложенные пользовательские объекты, которым нужен отдельный TypeAdapter , вы должны зарегистрировать TypeAdapter с новым экземпляром GSON , иначе второй 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 implements JsonDeserializer { 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 myDeserializer = new MyDeserializer(SubContent.class, new SubContentDeserializer()); Gson gson = new GsonBuilder().registerTypeAdapter(Content.class, myDeserializer).create(); 

Это можно легко использовать для вложенного «содержимого», просто пропустив в новом экземпляре MyDeserializer нулевые значения.

Немного поздно, но, надеюсь, это поможет кому-то.

Просто создайте следующий TypeAdapterFactory.

  public class ItemTypeAdapterFactory implements TypeAdapterFactory { public  TypeAdapter create(Gson gson, final TypeToken type) { final TypeAdapter delegate = gson.getDelegateAdapter(this, type); final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); return new TypeAdapter() { 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()); 

Продолжая идею Брайана, потому что у нас почти всегда есть много ресурсов REST, каждая из которых имеет собственный корень, было бы полезно обобщить десериализацию:

  class RestDeserializer implements JsonDeserializer { private Class mClass; private String mKey; public RestDeserializer(Class 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 gson = new GsonBuilder() .registerTypeAdapter(Content.class, new RestDeserializer<>(Content.class, "content")) .build(); 

Была такая же проблема пару дней назад. Я решаю это с помощью classа оболочки ответа и трансформатора RxJava, который, я думаю, является довольно гибким решением:

Упаковочный:

 public class ApiResponse { 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  Observable.Transformer, 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> 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 – «Глобальная» десериализация, тип ответа на изменение

Это то же самое решение, что и @AYarulin, но предположим, что имя classа – это имя ключа JSON. Таким образом вам нужно только передать имя classа.

  class RestDeserializer implements JsonDeserializer { private Class mClass; private String mKey; public RestDeserializer(Class 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. Это проблематично, поскольку ключ чувствителен к регистру, поэтому случай имени classа должен соответствовать случаю ключа JSON.

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

Лучшим решением может быть это.

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

Затем определите свое обслуживание следующим образом.

 Observable> updateDevice(..); 

Вот версия Котлина, основанная на ответах Брайана Роуча и А.Ярулина.

 class RestDeserializer(targetClass: Class, key: String?) : JsonDeserializer { 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) } } 

Согласно ответу @Brian Roach и @rafakob, я сделал это следующим образом

Ответ Json от сервера

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

Общий class обработчика данных

 public class ApiResponse { @SerializedName("status") public boolean status; @SerializedName("code") public int code; @SerializedName("message") public String reason; @SerializedName("data") public T content; } 

Специальный сериализатор

 static class MyDeserializer implements JsonDeserializer { @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()) .create(); 

Апи звонок

  @FormUrlEncoded @POST("/loginUser") Observable> signIn(@Field("email") String username, @Field("password") String password); restService.signIn(username, password) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(new Observer>() { @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 response) { Profile profile= response.content; Log.i("login", profile.getFullname()); } }); 

В моем случае ключ «контент» будет изменяться для каждого ответа. Пример:

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

Сала. Не забывайте @SerializedName и @Expose annotations для всех членов classа и членов Inner Class, которые большинство deserialized от JSON от GSON.

Посмотрите https://stackoverflow.com/a/40239512/1676736

  • Загрузить профиль Chrome с помощью Selenium WebDriver с помощью java
  • Spinner onItemSelected () выполняет ненадлежащим образом
  • Как просмотреть файл Realm в браузере Realm?
  • Java - чтение файла и разбиение на несколько файлов
  • Как нарисовать заполненный треугольник в андроидном canvasе?
  • Каков разумный порядок модификаторов Java (абстрактный, окончательный, общеansible, статический и т. Д.)?
  • как возобновить прерванную загрузку - часть 2
  • Не удалось выполнить dex: превышение верхнего предела GC в Eclipse
  • findviewbyid возвращает null в диалоговом окне
  • Android - запуск службы при загрузке
  • java.lang.NumberFormatException: для строки ввода
  • Давайте будем гением компьютера.