Retrofit에서 동적 JSON을 처리하는 방법은 무엇입니까?


82

개조 효율적인 네트워킹 라이브러리를 사용하고 있지만 무작위로 responseMessage변경되는 단일 접두사 를 포함하는 Dynamic JSON을 처리 할 수 ​​없으며 object, responseMessage경우에 따라 (동적으로) 동일한 접두사 ( )가 String으로 변경됩니다.

responseMessage의 JSON 형식 객체 :

{
   "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 (...)failure (...) 메서드 에서 문자열에서 객체로 임의로 변경됩니다 !

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 유형의 문자열 또는 객체로 설정되어 있으므로 동일한 이름 (java basics :))의 ref 변수로 POJO를 만들 수 있으므로 JSONRetrofit에서 dynamic 에 대한 동일한 솔루션을 찾고 있습니다. 비동기 작업을 사용하는 일반 http 클라이언트에서는 이것이 매우 쉬운 작업이라는 것을 알고 있지만 REST-Api JSON구문 분석 에서는 모범 사례가 아닙니다 ! 성능 벤치 마크를 보면 항상 Volley 또는 Retrofit이 최선의 선택이지만 동적 핸들링에 실패했습니다 JSON!

내가 아는 가능한 해결책

  1. http 클라이언트 구문 분석과 함께 이전 asyc 작업을 사용하십시오. :(

  2. RESTapi 백엔드 개발자를 설득하십시오.

  3. 사용자 지정 Retrofit 클라이언트 만들기 :)


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

이 샘플 변환기를 작성하여 String, Object, JsonObject 또는 Map <String, Object>로 Json 응답을 반환합니다. 분명히 모든 반환 유형이 모든 Json에서 작동하는 것은 아니며 개선의 여지가 있습니다. 그러나 변환기를 사용하여 거의 모든 응답을 동적 Json으로 변환하는 방법을 보여줍니다.


13
보는 RestAdapter이 예하면 개조 2에서 같은 컨버터를 구현하는 것이 어떻게 개조 1.입니까?
androidtitan

1
개조 2에서는 ConversionException을 사용할 수 없습니다. :(
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에서 어떻게 똑같이 할 수 있습니까?
Vadim Kotov

1
"checkResponseMessage"는 무엇입니까?
Shashank singh

15

gson-converter아래와 같이 사용자 지정 역 직렬화를 시도하십시오 (Retrofit 2.0에 대한 업데이트 된 답변).

아래와 같이 3 개의 모델 생성

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 (사용자 지정 deserialiser)

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'컴파일

***** 이전 답변 (위 답변이 더 권장 됨) *****

포조를 이렇게 변경하세요

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 크리슈나

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가 예외를 throw하고 onResponse ()가 아닌 onFailure ()에 도달하기 때문에 작동하지 않을 것이라고 생각합니다.
Amt87

9

가능한 모든 솔루션이 작동합니다. 또한 할 수있는 일은 Retrofit api 인터페이스 반환 유형을 응답으로 보내는 것입니다. 이 응답 Inputstream을 통해 JSON 객체로 변환하고 적합하다고 생각되는대로 읽을 수 있는 본문 을 얻을 수 있습니다.

http://square.github.io/retrofit/#api-declaration-RESPONSE OBJECT TYPE 아래를 보십시오.

업데이트 됨

Retrofit 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 값인지에 따라 거기에서 Object로 작업했습니다.

이것이 간단한 대답을 찾는 데 도움이되기를 바랍니다. :).


3

백엔드 API를 변경할 수 없다면 다음 변형을 고려할 것입니다 (Gson을 사용하여 JSON을 변환하는 경우).

  1. Gson 유형 어댑터 를 사용 ResponseMessage하여 inoming JSON을 구문 분석하는 방법을 동적으로 결정하는 유형에 대한 사용자 정의 어댑터 를 만들 수 있습니다 (예 :) if (reader.peek() == JsonToken.STRING).

  2. 응답 유형을 설명하는 일부 메타 정보를 HTTP 헤더에 넣고이를 사용하여 Gson 인스턴스에 제공해야하는 유형 정보를 결정합니다.



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

위의 경우 resultjson 의 키에 data1, data2가 포함되어 있으면 ValidateUserResult.java초기화됩니다. 오류가 발생하면 Error.java클래스가 초기화됩니다.


0

늦었다는 건 알지만 생각 만 나누고 싶어요. 메서드를 작성하는 프로젝트에서 작업 중이었습니다. 이 방법은 개조를 사용하여 서버에서 데이터를 가져옵니다. 우리 회사의 다른 개발자가이 방법을 사용하기 때문에 POJO클래스 (예 : TrackerRefResponse클래스)를 사용할 수 없습니다 . 그래서 나는 JsonObject/Object 다음과 같이 :

인터페이스 APIService.java

public class APIService{
    @FormUrlEncoded
    @POST
    Call<JsonObject> myPostMethod(@Url String url, @Field("input") String input);
}

그런 다음 내 방법으로 다음과 같이 썼습니다.

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();
                String jsonString = response.body().toString();
                // then do your stuff. maybe cast to object using a factory pattern  
    }
// rest of the code
}

Object'JsonObject'대신 사용할 수도 있습니다 . 나중에 그것이 어떤 종류의 응답인지 알게 될 때 아마도 이것을 원하는 객체로 캐스팅 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.