使用Gson和Retrofit 2来反序列化复杂的API响应 [英] Using Gson and Retrofit 2 to deserialize complex API responses

查看:108
本文介绍了使用Gson和Retrofit 2来反序列化复杂的API响应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Retrofit 2 Gson ,并且无法反序列化来自API的响应。这是我的场景:

我有一个名为 Employee 的模型对象,它有三个字段: id name age



我有一个API,它返回一个单独的 Employee 对象,如下所示:

 status:success,
code:200,
data:{
id:123,
id_to_name:{
123:John Doe
},
id_to_age:{
123:30
}


$ / code>

以及 Employee <

  {
status:success,
code:200,
data:[
{
id:123,
id_to_name:{
123: John Doe

id_to_age:{
123:30
}
},
{
id:456,
id_to_name:{
456:Jane Smith
},
id_to_age:{
456:35
}
},
]
}

在这里需要考虑三件事:


  1. API响应以通用包装器的形式返回,重要部分在 data 字段中。

  2. API返回的格式与模型中的字段不直接对应例如,取自 id_to_age 的值需要映射到模型中的 age 字段)

  3. API响应中的 data 字段可以是单个对象或对象列表。

如何使用 Gson 实现反序列化,以便它优雅地处理这三种情况?



理想情况下,我倾向于完全使用 TypeAdapter TypeAdapterFactory code>而不是支付 JsonDeserializer 的性能损失。最终,我想最终得到 Employee List< Employee> 的实例,以满足以下接口:

  public interface EmployeeService {

@GET(/ v1 / employees / {employee_id})
可观察<员工> getEmployee(@Path(employee_id)String employeeId);

@GET(/ v1 / employees)
可观察< List< Employee>>装getEmployees();


$ / code>

我之前发布的这个问题讨论了我的第一次尝试,但它没有考虑上面提到的几个问题:
使用Retrofit和RxJava,当它不直接映射到模型对象时,如何反序列化JSON?

解决方案

编辑:相关更新:创建自定义转换工厂确保工作 - 通过 ApiResponseConverterFactory避免无限循环是调用Retrofit的 nextResponseBodyConverter ,它允许您指定一个工厂跳过。关键是这是一个 Converter.Factory 来注册Retrofit,而不是 TypeAdapterFactory 。这实际上是更可取的,因为它可以防止ResponseBody的双重反序列化(不需要反序列化身体,然后将它重新打包为另一个响应)。

查看要点这里是一个实现例子。



原始答案:

除非您愿意使用 ApiResponse< T> 来包装所有服务接口,否则 ApiResponseAdapterFactory 方法不起作用。然而,还有另一种选择:OkHttp拦截器。



以下是我们的策略:


  • 对于特定的改造配置,您将注册拦截响应

  • 响应#body的应用程序拦截器()将被反序列化为 ApiResponse ,并返回一个新的 Response ,其中 ResponseBody 就是我们想要的内容。



所以 ApiResponse 看起来像:

  public class ApiResponse {
String status;
int code;
JsonObject数据;
}

ApiResponseInterceptor:

  public class ApiResponseInterceptor implements Interceptor {
public static final MediaType JSON = MediaType.parse(application / json; charset = utf-8);
public static final Gson GSON = new Gson();

@Override
公共响应拦截(链式链)抛出IOException {
请求请求= chain.request();
响应响应= chain.proceed(request);
final ResponseBody body = response.body();
ApiResponse apiResponse = GSON.fromJson(body.string(),ApiResponse.class);
body.close();

// TODO关于ApiResponse#status或#code的任何逻辑您需要做

final Response.Builder newResponse = response.newBuilder()
.body( ResponseBody.create(JSON,apiResponse.data.toString()));
return newResponse.build();




$ b

配置你的OkHttp并改进: b
$ b

  OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new ApiResponseInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(客户端)
.build();

员工 EmployeeResponse 应该遵循我在上一个问题中编写的适配器工厂构造。现在,所有 ApiResponse 字段都应该被拦截器使用,并且您所做的每个Retrofit调用都应只返回您感兴趣的JSON内容。


I'm using Retrofit 2 and Gson and I'm having trouble deserializing responses from my API. Here's my scenario:

I have a model object named Employee that has three fields: id, name, age.

I have an API that returns a singular Employee object like this:

{
    "status": "success",
    "code": 200,
    "data": {
        "id": "123",
        "id_to_name": {
            "123" : "John Doe"
        },
        "id_to_age": {
            "123" : 30
        }
    }
}

And a list of Employee objects like this:

{
    "status": "success",
    "code": 200,
    "data": [
        {
            "id": "123",
            "id_to_name": {
                "123" : "John Doe"
            },
            "id_to_age": {
                "123" : 30
            }
        },
        {
            "id": "456",
            "id_to_name": {
                "456" : "Jane Smith"
            },
            "id_to_age": {
                "456" : 35
            }
        },
    ]
}

There are three main things to consider here:

  1. API responses return in a generic wrapper, with the important part inside of the data field.
  2. The API returns objects in a format that doesn't directly correspond to the fields on the model (for example, the value taken from id_to_age needs be mapped to the age field on the model)
  3. The data field in the API response can be a singular object, or a list of objects.

How do I implement deserialization with Gson such that it handles these three cases elegantly?

Ideally, I'd prefer to do this entirely with TypeAdapter or TypeAdapterFactory instead of paying the performance penalty of JsonDeserializer. Ultimately, I want to end up with an instance of Employee or List<Employee> such that it satisfies this interface:

public interface EmployeeService {

    @GET("/v1/employees/{employee_id}")
    Observable<Employee> getEmployee(@Path("employee_id") String employeeId);

    @GET("/v1/employees")
    Observable<List<Employee>> getEmployees();

}

This earlier question I posted discusses my first attempt at this, but it fails to consider a few of the gotchas mentioned above: Using Retrofit and RxJava, how do I deserialize JSON when it doesn't map directly to a model object?

解决方案

EDIT: Relevant update: creating a custom converter factory DOES work--the key to avoiding an infinite loop through ApiResponseConverterFactory's is to call Retrofit's nextResponseBodyConverter which allows you to specify a factory to skip over. The key is this would be a Converter.Factory to register with Retrofit, not a TypeAdapterFactory for Gson. This would actually be preferable since it prevents double-deserialization of the ResponseBody (no need to deserialize the body then repackage it again as another response).

See the gist here for an implementation example.

ORIGINAL ANSWER:

The ApiResponseAdapterFactory approach doesn't work unless you are willing to wrap all your service interfaces with ApiResponse<T>. However, there is another option: OkHttp interceptors.

Here's our strategy:

  • For the particular retrofit configuration, you will register an application interceptor that intercepts the Response
  • Response#body() will be deserialized as an ApiResponse and we return a new Response where the ResponseBody is just the content we want.

So ApiResponse looks like:

public class ApiResponse {
  String status;
  int code;
  JsonObject data;
}

ApiResponseInterceptor:

public class ApiResponseInterceptor implements Interceptor {
  public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
  public static final Gson GSON = new Gson();

  @Override
  public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    Response response = chain.proceed(request);
    final ResponseBody body = response.body();
    ApiResponse apiResponse = GSON.fromJson(body.string(), ApiResponse.class);
    body.close();

    // TODO any logic regarding ApiResponse#status or #code you need to do 

    final Response.Builder newResponse = response.newBuilder()
        .body(ResponseBody.create(JSON, apiResponse.data.toString()));
    return newResponse.build();
  }
}

Configure your OkHttp and Retrofit:

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new ApiResponseInterceptor())
        .build();
Retrofit retrofit = new Retrofit.Builder()
        .client(client)
        .build();

And Employee and EmployeeResponse should follow the adapter factory construct I wrote in the previous question. Now all of the ApiResponse fields should be consumed by the interceptor and every Retrofit call you make should only return the JSON content you are interested in.

这篇关于使用Gson和Retrofit 2来反序列化复杂的API响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆