it-swarm.com.ru

Как работать с динамическим JSON в Retrofit?

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

JSON формат Объект ответаMessage:

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

}

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

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

Проблема для меня заключается в том, что при модернизации есть встроенный синтаксический анализ JSON, поэтому мы должны назначать один POJO для каждого запроса! но REST-API, к сожалению, построен с динамическими ответами JSON, префикс изменится на строковый объект случайным образом как в методах success (...), так и fail (...)

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

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

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

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

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

68
LOG_TAG

Поздно на вечеринку, но вы можете использовать конвертер.

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.

35
Oliver Hausler

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

17
Yuki Yoshida

Попробуйте пользовательскую десериализацию, используя 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);
        }

}
}

Внедрение модифицированной версии 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'

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

Измените свое 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

10
Navneet Krishna

Любое из ваших возможных решений будет работать. Что вы также можете сделать, это отправить тип возврата интерфейса Retrofit API в ответ. С этим ответом вы получите тело Inputstream, которое вы можете преобразовать в объект JSON и прочитать, как считаете нужным.

Посмотрите: http://square.github.io/retrofit/#api-declaration - в разделе ТИП ОТВЕТА

Обновленный

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

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

10
Justin Slade

Принятый ответ показался мне слишком сложным, я решаю его так:

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 - это просто логическое значение для вашего условия использования соответствующей модели.

8
meda

Я знаю, что очень очень поздно на вечеринку. У меня была похожая проблема, и я решил ее так:

public class TrackerRefResponse {

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

}

Я буквально просто изменил тип на объект. Я выбрал этот подход, потому что только одно поле в ответе было динамическим (для меня мой ответ был намного более сложным), поэтому использование конвертера усложнило бы жизнь. Использовал Gson для работы с Объектом оттуда, в зависимости от того, было ли это значение String или Array.

Надеюсь, что это помогает кому-то, ищущему простой ответ :).

6
Lunchbox

Если бы не было возможности изменить внутренний API, я бы рассмотрел следующие варианты (если Gson используется для преобразования JSON).

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

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

3
Roman Mazur

В дополнение к тому, что вы сказали -

Используйте Callback Затем вы можете получить поля с помощью обычного метода get . Для получения дополнительной информации просмотрите javadoc of gson.

http://google-gson.googlecode.com/svn/tags/1.2.3/docs/javadocs/com/google/gson/JsonObject.html

1
Sugesh

Я тоже столкнулся с этой проблемой . Но я не уверен, что это был ваш случай, (я использую 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.Java инициализируется . если ошибка, то класс Error.Java инициализируется.

0
Akhil T Mohan