Як обробляти Dynamic JSON у модернізації?


82

Я використовую модернізовану ефективну мережеву бібліотеку, але я не можу обробити Dynamic JSON, який містить один префікс, responseMessageякий змінюється objectвипадковим чином, той самий префікс ( responseMessage) в деяких випадках змінюється на String (динамічно).

Формат Json Об'єкт відповіді Повідомлення:

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

}

responseMessage Формат Json динамічно змінюється на тип string:

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

Моя проблема полягає в тому, що в модернізацію вбудований JSONсинтаксичний аналіз, я повинен призначити один POJO на запит! але REST-API, на жаль, побудований на динамічних 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 із змінною ref з тим самим іменем (основи Java :)), тому я шукаю те саме рішення для динаміки JSONв модернізації. Я знаю, що це дуже легка робота в звичайних http-клієнтах із асинхронним завданням, але це не найкраща практика JSONрозбору REST-Api ! дивлячись на продуктивність Бенчмарки завжди Volley або Retrofit є кращим вибором, але я не зміг ручка динамічний JSON!

Можливе рішення, яке я знаю

  1. Використовуйте старе завдання asyc для синтаксичного аналізу клієнта 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();
    }
}

Я написав цей зразок перетворювача, тому він повертає відповідь Json як String, Object, JsonObject або Map <String, Object>. Очевидно, що не всі типи повернення будуть працювати для кожного Json, і є певний простір для вдосконалення. Але це демонструє, як використовувати конвертер для перетворення майже будь-якої відповіді на динамічний Json.


13
Побачення RestAdapterцього прикладу стосується Модернізації 1. Як би Ви реалізували той самий конвертер у Модернізації 2?
androidtitan

1
ConversionException недоступний у модернізації 2 :(
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".


Як я можу зробити те саме в Retrofit 2?
Вадим Котов

1
Що таке "checkResponseMessage"?
Шашанк Сінгх,

15

Спробуйте спеціальну десериалізацію, використовуючи gson-converterнаведені нижче (оновлена ​​відповідь для модернізації 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 (спеціальний десериалізатор)

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: converter-gson: 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;
 }
}

і змінити модернізацію onResponse таким чином

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

Ви також можете перевірити цю публікацію, щоб отримати докладнішу інформацію про динамічний аналіз json


Чи дійсно необхідний клас ResponseWrapper? Я думаю, це виглядає дуже заплутаним. Мені потрібен перетворювач на що завгодно, крім об'єкта, що знаходиться найвище в ієрархії ...
RabbitBones22

1
ви можете уникнути класу обгортки, якщо це бентежить, і спробуйте це freshbytelabs.com/2018/12/…
Navneet Krishna

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 Я спростив код, а потім розширив поясненнями
meda

2
Тим не менше, я вважаю, що це не допоможе, якщо ви не знаєте тип відповіді перед тим, як зателефонувати, як це має місце у питанні. Звичайно, ви могли спочатку отримати InputStream тіла відповіді, прочитати кілька рядків, визначити, яким типом є тіло, а потім перетворити його. Але це просто не так просто.
Desdroid

Я шукаю найкращий спосіб обробки різних типів повернення. Ваша відповідь виглядала досить багатообіцяючою, але я не був впевнений, звідки ви знаєте Тип. Ось чому я хотів, щоб ви це детально розробили;)
Desdroid

2
Я думаю, це не спрацює, тому що Deserializer видасть виняток і дійде до onFailure (), а не onResponse ()
Amt87,

9

Будь-яке з ваших можливих рішень буде працювати. Що ви також можете зробити, це надіслати відповідь типу інтерфейсу Retrofit API інтерфейсу відповіді. З цією відповіддю ви отримуєте тіло, Inputstreamяке ви можете перетворити на об’єкт JSON і прочитати, як вважаєте за потрібне.

Подивіться на: http://square.github.io/retrofit/#api-declaration - у розділі ТИП ОБЕКТУ ВІДПОВІДІ

Оновлено

Модернізація 2 вийшла зараз, а разом із нею і деякі зміни в документації та бібліотеці.

Подивіться на http://square.github.io/retrofit/#restadapter-configuration - є об'єкт тіла запиту та відповіді, який можна використовувати.


6
Боюся, що я не можу знайти розділ, який ви надали, чи є якийсь синонім?
zionpi

7

Я знаю, що я дуже дуже пізно на вечірку. У мене була подібна проблема, і я просто вирішив її так:

public class TrackerRefResponse {

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

}

Я буквально просто змінив тип на Object. Я обрав такий підхід, оскільки лише одне поле у ​​відповіді було динамічним (для мене моя відповідь була набагато складнішою), тому використання перетворювача ускладнило б життя. Використовував Gson для роботи з Об’єктом звідти, залежно від того, чи це було значення String або Array.

Сподіваюся, це допомагає тому, хто шукає просту відповідь :).


3

Якщо не вдалося змінити серверний API, я б розглянув наступні варіанти (якщо Gson використовується для перетворення JSON).

  1. Ми можемо використовувати адаптери типу Gson, щоб створити власний адаптер для ResponseMessageтипу, який динамічно вирішує, як проаналізувати ігноруючий JSON (використовуючи щось на зразок if (reader.peek() == JsonToken.STRING)).

  2. Помістіть деяку метаінформацію, що описує тип відповіді, у заголовок HTTP і використовуйте її, щоб визначити, яку інформацію про тип потрібно передавати екземпляру Gson.



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". Пізніше, коли ви дізнаєтеся, що це за відповідь, можливо, ви можете передати це у бажаний об'єкт.


Це рішення більше відповідало моїй проблемі. Одна з відповідей api з null, якщо все нормально, і повернення успіху (код 200) із повідомленням про помилку у простому об'єкті Json, коли є проблема. Я використовую JsonNull як відповідь за замовчуванням.
YazidEF

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

у наведеному вище випадку, якщо resultключ на json містить дані1, дані2, тоді ValidateUserResult.javaget ініціалізується. якщо помилка, то Error.javaклас отримується ініціалізованим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.