使用Gson和Retrofit 2来反序列化复杂的API响应 [英] Using Gson and Retrofit 2 to deserialize complex API responses
问题描述
我正在使用 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
}
},
]
}
在这里需要考虑三件事:
- API响应以通用包装器的形式返回,重要部分在
data
字段中。 - API返回的格式与模型中的字段不直接对应例如,取自
id_to_age
的值需要映射到模型中的age
字段) - 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:
- API responses return in a generic wrapper, with the important part inside of the
data
field.
- 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)
- 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屋!