使用Gson和Retrofit 2反序列化复杂的API响应

use*_*435 13 android gson deserialization retrofit

我正在使用Retrofit 2和Gson,我无法从我的API反序列化响应.这是我的情景:

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

我有一个返回如下单个Employee对象的API :

{
    "status": "success",
    "code": 200,
    "data": {
        "id": "123",
        "id_to_name": {
            "123" : "John Doe"
        },
        "id_to_age": {
            "123" : 30
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以及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
            }
        },
    ]
}
Run Code Online (Sandbox Code Playgroud)

这里有三个主要的事情需要考虑:

  1. API响应在通用包装器中返回,其中重要部分位于data字段内.
  2. API以不与模型上的字段直接对应的格式返回对象(例如,从id_to_age需要的值映射到age模型上的字段)
  3. dataAPI响应中的字段可以是单个对象,也可以是对象列表.

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

理想情况下,我宁愿完全使用TypeAdapterTypeAdapterFactory代替支付性能损失JsonDeserializer.最后,我想用一个实例来结束EmployeeList<Employee>使得它满足这个接口:

public interface EmployeeService {

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

ekc*_*ang 7

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

请参阅此处的要点以获取实施示例.

原始答案:

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

这是我们的策略:

  • 对于特定的改造配置,您将注册拦截该应用程序的应用程序拦截器 Response
  • Response#body()将被反序列化为一个ApiResponse,我们返回一个新的Response,ResponseBody只是我们想要的内容.

所以ApiResponse看起来像:

public class ApiResponse {
  String status;
  int code;
  JsonObject data;
}
Run Code Online (Sandbox Code Playgroud)

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

配置OkHttp和Retrofit:

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new ApiResponseInterceptor())
        .build();
Retrofit retrofit = new Retrofit.Builder()
        .client(client)
        .build();
Run Code Online (Sandbox Code Playgroud)

EmployeeEmployeeResponse应遵循的适配器工厂构造我在上一个问题中写道.现在ApiResponse拦截器应该使用所有字段,并且您进行的每个Retrofit调用应该只返回您感兴趣的JSON内容.


小智 6

我建议使用a,JsonDeserializer因为响应中没有那么多级别的嵌套,所以它不会是一个很大的性能损失.

类看起来像这样:

需要针对通用响应调整服务接口:

interface EmployeeService {

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

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

}
Run Code Online (Sandbox Code Playgroud)

这是一般数据响应:

class DataResponse<T> {

    @SerializedName("data") private T data;

    public T getData() {
        return data;
    }
}
Run Code Online (Sandbox Code Playgroud)

员工模型:

class Employee {

    final String id;
    final String name;
    final int age;

    Employee(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

}
Run Code Online (Sandbox Code Playgroud)

员工解串器:

class EmployeeDeserializer implements JsonDeserializer<Employee> {

    @Override
    public Employee deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {

        JsonObject employeeObject = json.getAsJsonObject();
        String id = employeeObject.get("id").getAsString();
        String name = employeeObject.getAsJsonObject("id_to_name").entrySet().iterator().next().getValue().getAsString();
        int age = employeeObject.getAsJsonObject("id_to_age").entrySet().iterator().next().getValue().getAsInt();

        return new Employee(id, name, age);
    }
}
Run Code Online (Sandbox Code Playgroud)

与响应的问题是,nameage包含一个JSON对象,因此它需要多一点的工作,以分析它whitch转换为Java中的地图内.

  • 你在哪里调用你的解串器? (2认同)