如何在Retrofit中处理动态JSON?

89

我正在使用Retrofit高效的网络库,但是无法处理包含单个前缀responseMessage的动态JSON,该前缀会随机变为object,在某些情况下(动态地)相同的前缀responseMessage会变为字符串。

responseMessage的Json格式对象:

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

}

responseMessage 的 JSON 格式会动态地变成字符串类型:

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

我的问题是,由于Retrofit具有内置的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();
                }


            });
}

普通Java对象:

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 基础知识 :))的 ref 变量的 POJO,因此我正在寻找用于 Retrofit 动态 JSON 的同一解决方案。我知道在普通的 HTTP 客户端中使用异步任务非常容易,但在 REST-API JSON 解析中不是最佳实践!从性能的角度来看,基准测试总是 Volley 或 Retrofit 是最佳选择,但我无法处理动态 JSON!可能的解决方案包括:
  1. 使用旧的异步任务与 HTTP 客户端解析。 :(
  2. 尝试说服 REST API 后端开发人员。
  3. 创建自定义 Retrofit 客户端 :)。

1
尝试说服RESTapi后端开发人员对我很有帮助!哈哈! ;) (注:我也是后端开发人员,我要说服自己!) - mrx
12个回答

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响应以字符串、对象、JsonObject或Map< String,Object >的形式返回。显然,并非所有的返回类型都适用于每个Json,还有改进的空间。但它展示了如何使用转换器将几乎任何响应转换为动态的Json。


14
看到这个例子中使用的是RestAdapter,它是用于Retrofit 1的。如果要在Retrofit 2中实现相同的转换器,应该如何操作? - androidtitan
2
抱歉,Retrofit 2 中没有 ConversionException 可用 :( - mutkan

20

RestClient.java

import retrofit.client.Response;
public interface RestClient {
  @GET("/api/foo") Response getYourJson();
}

你的类名.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

19

尝试使用 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;
    }

}

响应消息

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

}

响应字符串

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

使用的库

compile 'com.squareup.retrofit2:retrofit:2.3.0'

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

并且像这样更改retrofit的onResponse。
 @Override
public void onResponse(Response<TrackerRefResponse > response) {
    if (response.isSuccess()) {
        if (response.getResponseMessage() instanceof String)
            {
            handleStringResponse();
         }
        else 
            {
            handleObjectResponse();
         }
    }
}

您还可以查看此篇文章了解有关动态JSON解析的更多详细信息


ResponseWrapper类真的必要吗?我认为它看起来非常令人困惑。除了在层次结构中最高的对象之外,我需要一个转换器... - RabbitBones22
1
如果包装类让您感到困惑,可以尝试使用以下链接:https://www.freshbytelabs.com/2018/12/using-custom-gson-converter-to-parse.html,避免使用它。 - Navneet Krishna

10

您可能的任何解决方案都可以生效。您还可以将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

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
我正在寻找处理不同返回类型的最佳方法。你的答案看起来很有前途,但我不确定你是从哪里知道Type的。这就是为什么我想让你详细说明一下的原因 ;) - Desdroid
2
我认为这不会起作用,因为反序列化程序将抛出异常并到达onFailure()而不是onResponse()。 - Amt87

8

我知道我来晚了。我之前也遇到过类似的问题,我的解决方法如下:

public class TrackerRefResponse {

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

}

我刚刚把类型改成了Object。我选择这种方法是因为响应中只有一个字段是动态的(对我来说,我的响应要复杂得多),所以使用转换器会使生活变得困难。从那里使用Gson来处理对象,具体取决于它是字符串还是数组值。

希望这能帮助需要简单答案的人 :)


3

对于kotlin开发者,您可以将变量的类型声明为Any,然后使用Gson转换器将其转换为Gson类。

data class SomeClass(
     ...somevariables,
     val respnseMessage : Any
)

然后在您的活动或片段中,无论您想要解析和使用它。根据您的用例,您可以像这样执行某些操作。

 val gson = Gson()
 val data = gson.fromJson<YourDesireClass> 
 (response.responseMessage.toString() , YourDesireClass::class.java)

3

如果无法更改后端API,则考虑以下方案(如果使用Gson将JSON转换)。

  1. 我们可以使用Gson type adapters创建一个自定义适配器,用于ResponseMessage类型,它动态决定如何解析传入的JSON(类似于if(reader.peek() == JsonToken.STRING))。

  2. 将描述响应类型的一些元信息放入HTTP头中,并使用它来确定必须提供给Gson实例的类型信息。


1
我知道我来晚了,但我想分享我的想法。我正在开发一个项目,其中编写了一个方法。该方法使用retrofit从服务器获取数据。由于公司中的其他开发人员将使用此方法,因此我不能使用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,并在简单的JSON对象中返回成功(代码200)和错误消息,当出现问题时。我使用JsonNull作为默认响应。 - Yazid

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接