Как обрабатывать динамический JSON в модернизации?

82

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

Формат Json Объект responseMessage:

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

}

responseMessage Формат Json динамически изменяется на строку типа:

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


            });
}

Поджо:

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модификации в Retrofit. Я знаю, что это очень простая работа в обычных http-клиентах с асинхронной задачей, но это не лучшая практика при JSONразборе REST-Api ! Всегда смотрю на производительность. Тесты производительности: Volley или Retrofit - лучший выбор, но мне не удалось справиться с динамикой JSON!

Возможное решение я знаю

  1. Используйте старую задачу asyc с синтаксическим анализом http-клиента. :(

  2. Попробуйте убедить бэкэнд-разработчика RESTapi.

  3. Создайте собственный клиент модернизации :)

LOG_TAG
источник
1
«Попробуйте убедить разработчика бэкэнда RESTapi». помогли мне! смешно! ;) (Примечание: я тоже был бэкэнд-разработчиком, я должен убедить себя!)
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, и есть определенные возможности для улучшения. Но он демонстрирует, как использовать Converter для преобразования практически любого ответа в динамический Json.

Оливер Хауслер
источник
13
Увидев RestAdapterэтот пример для Модернизированного 1. Как бы реализовать один и тот же преобразователь в Модернизированном 2?
androidtitan
1
ConversionException недоступно в Retrofit 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как показано ниже (обновленный ответ для 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 (настраиваемый десериализатор)

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

}
}

Внедрение Retrofit 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: retrofit: 2.3.0 '

скомпилировать com.squareup.retrofit2: converter-gson: 2.3.0 '

***** Предыдущий ответ (приведенный выше ответ является более рекомендуемым) *****

Измените свое pojo вот так

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 - в разделе ТИП ОБЪЕКТА ОТВЕТА

Обновлено

Retrofit 2 уже вышел, а вместе с ним и некоторые изменения в документации и библиотеке.

Посмотрите http://square.github.io/retrofit/#restadapter-configuration, есть объекты тела запроса и ответа, которые можно использовать.

Джастин Слейд
источник
6
Боюсь, я не могу найти предоставленный вами раздел, есть ли синонимы?
zionpi 02
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

Если бы не было возможности изменить backend 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 с нулевым значением, если все в порядке, и возврат успеха (код 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 содержит data1, data2, тогда ValidateUserResult.javaget инициализируется. в случае ошибки Error.javaкласс инициализируется.

Ахил Т Мохан
источник