我正在使用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
}
}
}
这样的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
}
},
]
}
这里要考虑三件事:
data
字段内。id_to_age
获取的值需要映射到模型上的age
字段) data
字段可以是单个对象,也可以是对象列表。如何使用Gson
实现反序列化,以便优雅地处理这三种情况?
理想情况下,我更愿意使用TypeAdapter
或TypeAdapterFactory
完全执行此操作,而不是支付JsonDeserializer
的性能损失。最终,我希望最终得到Employee
或List<Employee>
的实例,以便它满足此接口:
public interface EmployeeService {
@GET("/v1/employees/{employee_id}")
Observable<Employee> getEmployee(@Path("employee_id") String employeeId);
@GET("/v1/employees")
Observable<List<Employee>> getEmployees();
}
我发布的这个早期问题讨论了我的第一次尝试,但它没有考虑上面提到的一些问题: Using Retrofit and RxJava, how do I deserialize JSON when it doesn't map directly to a model object?
答案 0 :(得分:8)
编辑:相关更新:创建自定义转换器工厂是否正常工作 - 避免通过ApiResponseConverterFactory
无限循环的关键是调用允许改造的nextResponseBodyConverter
您指定要跳过的工厂。关键是这将是一个Converter.Factory
注册Retrofit,而不是Gson的TypeAdapterFactory
。这实际上是更可取的,因为它可以防止ResponseBody的双重反序列化(不需要反序列化主体,然后再将其重新打包为另一个响应)。
See the gist here for an implementation example.
原始答案:
除非您愿意使用ApiResponseAdapterFactory
包装所有服务接口,否则ApiResponse<T>
方法不起作用。但是,还有另一种选择:OkHttp拦截器。
这是我们的策略:
Response
Response#body()
将被反序列化为ApiResponse
,我们会返回一个新的Response
,ResponseBody
就是我们想要的内容。所以ApiResponse
看起来像是:
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();
}
}
配置OkHttp和Retrofit:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new ApiResponseInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.build();
Employee
和EmployeeResponse
应遵循the adapter factory construct I wrote in the previous question。现在拦截器应该使用所有ApiResponse
字段,并且每次进行的Retrofit调用都应该只返回您感兴趣的JSON内容。
答案 1 :(得分:5)
我建议使用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();
}
这是一般数据回复:
class DataResponse<T> {
@SerializedName("data") private T data;
public T getData() {
return data;
}
}
员工模式:
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;
}
}
员工解串器:
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);
}
}
响应的问题是name
和age
包含在JSON对象中,并且在Java中转换为Map,因此需要更多的工作来解析它。
答案 2 :(得分:3)
只需创建以下TypeAdapterFactory。
public class ItemTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("data")) {
jsonElement = jsonObject.get("data");
}
}
return delegate.fromJsonTree(jsonElement);
}
}.nullSafe();
}
}
并将其添加到您的GSON构建器中:
.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
或
yourGsonBuilder.registerTypeAdapterFactory(new ItemTypeAdapterFactory());
答案 3 :(得分:0)
我必须说,我还没有考虑过将Interceptors
用于这样的事情,但这是一种有趣的方法。当我需要对后端包装响应建模时,通常会做以下事情:
如果您从后端获得了类似的信息:
{
"success": "success", // Let's say you may get "error", "unauthorized", etc.
"payload": [...] // Let's say that you may either get a json object or an array.
}
然后您可以声明一个反序列化器:
import com.demo.core.utils.exceptions.NonSuccessfullResponse
import com.google.gson.Gson
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
/**
* A custom deserializers that uses the generic arg TYPE to deserialize on the fly the json responses from
* the API.
*/
class WrapperDeserializer<TYPE>(
private val castClazz: Class<TYPE>,
private val isList: Boolean
) : JsonDeserializer<TYPE> {
val gson = Gson()
override fun deserialize(
element: JsonElement,
arg1: Type,
arg2: JsonDeserializationContext
): TYPE? {
val jsonObject = element.asJsonObject
if (jsonObject.get("success").asBoolean) {
return if (isList) {
val type = TypeToken.getParameterized(List::class.java, castClazz).type
gson.fromJson(jsonObject.get("payload"), type)
} else {
gson.fromJson(jsonObject.get("payload"), castClazz)
}
} else {
throw NonSuccessfullResponse()
}
}
}
然后在任何实例化Gson实例的地方,您都可以执行以下操作:
fun provideGson(): Gson {
val bookListType = TypeToken.getParameterized(List::class.java, ApiAvailableBooksResponse::class.java).type
return GsonBuilder()
.registerTypeAdapter(bookListType, WrapperDeserializer(ApiAvailableBooksResponse::class.java, true))
.registerTypeAdapter(ApiProfileInfoResponse::class.java, WrapperDeserializer(ApiProfileInfoResponse::class.java, false))
.registerTypeAdapter(Date::class.java, DateDeserializer())
.create()
}
请注意,我们正在映射两种不同的响应,即书籍列表,例如:
{
"success": "success",
"payload": [
{...}, // Book 1
{...}, // Book 2
{...} // Book 3
]
}
以及一个用户个人资料响应:
{
"success": "success",
"payload": {
"name": "etc",
// ...
}
}
同样,Interceptor
方法是我以前从未考虑过的一个非常有趣的选项-由于要强制所有端点响应都遵循相同的标准,因此在灵活性方面让我有些担心-但看起来更整洁。