Retrofitで動的JSONを処理する方法は?


82

レトロフィットの効率的なネットワークライブラリを使用してresponseMessageいますが、objectランダムに変更される単一のプレフィックスを含む動的JSONを処理できません。 同じプレフィックス(responseMessage)が場合によっては(動的に)文字列に変更されます。

responseMessageのJson形式のオブジェクト:

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

}

responseMessage Json形式は動的に文字列型に変更されます。

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

私の問題は、レトロフィットにはJSON解析が組み込まれているため、リクエストごとに1つの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の文字列またはオブジェクトに設定されているため、同じ名前のref変数を使用してPOJOを作成できます(java basics :))。したがってJSON、Retrofitで動的な同じソリューションを探しています。これは、非同期タスクを使用する通常のhttpクライアントでは非常に簡単な作業ですが、REST-ApiJSON解析のベストプラクティスではありません。パフォーマンスを見てベンチマーク常にボレーまたはレトロフィットが最良の選択ですが、動的に処理できませんJSON

私が知っている可能な解決策

  1. httpクライアントの解析で古いasycタスクを使用します。:(

  2. RESTapiバックエンド開発者を説得してみてください。

  3. カスタムRetrofitクライアントを作成します:)


1
「RESTapiバックエンド開発者を説得してみてください。」私のためにトリックをしました!笑!;)(nb:私もバックエンド
開発者でした

回答:


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

次に、retrofitのConverterを実装する静的クラスを使用します。

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この例にすると、レトロフィット2で同じコンバータを実装する方法をレトロフィット1のためにあるのですか?
androidtitan 2016年

1
ConversionExceptionは
Retrofit2

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で同じことを行うにはどうすればよいですか?
VadimKotov19年

1
「checkResponseMessage」とは何ですか?
Shashanksingh19年

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(カスタムデシリアライザー

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

}
}

Retrofit2.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/
NavneetKrishna19年

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 2016

@Desdroid Iコードを簡素化し、次に説明で拡張
MEDA

2
それでも、電話をかける前に応答の種類がわからない場合は、これは役に立たないと思います。これは、質問の場合です。確かに、最初に応答本文のInputStreamを取得し、数行を読んで、本文がどのタイプであるかを判別してから、それを変換することができます。しかし、それはそれほど単純ではありません。
Desdroid 2016

さまざまな戻り値の型を処理するための最良の方法を探しています。あなたの答えはかなり有望に見えましたが、どこからタイプを知っているのかわかりませんでした。だから私はあなたにそれを詳しく説明して欲しかったのです;)
Desdroid 2016

2
デシリアライザーが例外をスローし、onResponse()ではなくonFailure()に到達するため、機能しないと思います
Amt87 2016

9

考えられる解決策はどれでも機能します。また、RetrofitAPIインターフェイスの戻り値の型を応答に送信することもできます。その応答をInputstream使用して、JSONオブジェクトに変換し、必要に応じて読み取ることができる本文を取得します。

見てくださいhttp//square.github.io/retrofit/#api-declaration -応答オブジェクトタイプの下

更新しました

Retrofit 2がリリースされ、ドキュメントとライブラリにいくつかの変更が加えられました。

http://square.github.io/retrofit/#restadapter-configurationを見てください。使用できるリクエストとレスポンスの本文オブジェクトがあります。


6
あなたが提供したセクションが見つからないのではないかと思いますが、同義語はありますか?
zionpi 2016年

7

私はパーティーにとても遅れていることを知っています。私は同様の問題を抱えていて、次のように解決しました:

public class TrackerRefResponse {

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

}

文字通り、タイプをオブジェクトに変更しました。このアプローチを選択したのは、応答の1つのフィールドのみが動的であったため(私にとって、応答ははるかに複雑でした)、コンバーターを使用すると生活が困難になるためです。文字列値か配列値かに応じて、Gsonを使用してそこからオブジェクトを操作しました。

これが簡単な答えを探している人に役立つことを願っています:)。


3

バックエンドAPIを変更できない場合は、次のバリアントを検討します(Gsonを使用してJSONを変換する場合)。

  1. Gsonタイプのアダプターを使用して、ResponseMessage(のようなものを使用してif (reader.peek() == JsonToken.STRING))inoming JSONを解析する方法を動的に決定するタイプのカスタムアダプターを作成できます。

  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`の代わりに使用することもできます。後で、それがどの種類の応答であるかがわかったら、これを目的のオブジェクトにキャストできます。


この解決策は私の問題によりよく適合します。すべてが正常である場合はnullのAPI応答の1つであり、問​​題がある場合は単純なJsonオブジェクトにエラーメッセージとともに成功(コード200)を返します。デフォルトの応答として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;
}

上記の場合、resultjsonのキーにdata1、data2が含まれていると、ValidateUserResult.javagetが初期化されます。エラーの場合、Error.javaクラスは初期化されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.