[英]Retrofit 2 response body to custom class
Currently, I use retrofit2 to call restful apis and get response. 目前,我使用retrofit2来调用restful apis并获得响应。 Because the response body can be multiple types, I wrote the code following. 因为响应体可以是多种类型,所以我编写了以下代码。
//Interface
@FormUrlEncoded
@POST("payments/events/{id}")
fun postPayment(@Path("id") id: String): Call<Any>
//Api Manager
fun postPayment(id: String): Observable<Any> {
return Observable.create {
subscriber ->
val callResponse = api.postPayment(id)
val response = callResponse.execute()
if (response.isSuccessful) {
if (response.body() is MyClass1) {
// never success...
} else if (response.body() is MyClass2) {
// never success...
}
subscriber.onNext(response.body())
subscriber.onCompleted()
} else {
subscriber.onError(Throwable(response.message()))
}
}
}
So I'm not able to cast response.body()
to MyClass1
or MyClass2
. 所以我无法将response.body()
为MyClass1
或MyClass2
。 response.body() as MyClass1
occurs error too. response.body() as MyClass1
发生错误。
MyClass1
and MyClass2
are normal template classes. MyClass1
和MyClass2
是普通的模板类。
class MyClass1( val id: String, val data: String)
Is there any smart way to cast response body to my custom classes? 有没有聪明的方法将响应体转换为我的自定义类?
Small update for MyClass2 MyClass2的小更新
class MyClass2( val token: String, val url: String, val quantity: Int)
As mentioned by @Miha_x64, Retrofit doesn't know about your classes ( MyClass1
and MyClass2
) because your Call
uses the Any
type. 正如@ Miha_x64所述,Retrofit不了解您的类( MyClass1
和MyClass2
),因为您的Call
使用Any
类型。 Therefore, Retrofit is not creating an instance of MyClass1
or MyClass2
, instead it is just creating an instance of the Any
class. 因此,Retrofit不会创建MyClass1
或MyClass2
的实例,而只是创建Any
类的实例。
The simplest solution would just be to combine the two classes: 最简单的解决方案就是将两个类组合在一起:
data class MyClass(
val id: String?,
val data: String?,
val token: String?,
val url: String?,
val quantity: Int
)
Then you can specify the response type in your interface: 然后,您可以在界面中指定响应类型:
@FormUrlEncoded
@POST("payments/events/{id}")
fun postPayment(@Path("id") id: String): Call<MyClass>
In the case your response does not have an id
or data
element, they will just be null
. 如果您的响应没有id
或data
元素,则它们将为null
。 Then you can check which type of response was received simply by checking which values are null
: 然后,您可以通过检查哪些值为null
来检查收到的响应类型:
if (response.body().id != null) {
// Handle type 1 response...
} else if (response.body().token != null) {
// Handle type 2 response...
}
A slightly more complex solution would be to write a wrapper for your two classes, and a type adapter to populate the wrapper. 稍微复杂的解决方案是为两个类编写一个包装器,并使用一个类型适配器来填充包装器。 This would avoid the nullability of each of the fields, as well as keep your data structure separated. 这样可以避免每个字段的可空性,并保持数据结构的分离。
This would differ based on the ConverterFactory
you are using but if, for example, you are using Gson, it would look something like this: 这将根据您使用的ConverterFactory
而有所不同,但是,例如,如果您使用的是Gson,它将看起来像这样:
data class ApiResponse(
val val1: MyClass1? = null,
val val2: MyClass2? = null
)
class ApiResponseAdapter : TypeAdapter<ApiResponse> {
@Throws(IOException::class)
override fun write(out: JsonWriter, value: ApiResponse?) {
if (value != null) {
out.beginObject()
value.val1?.id? let { out.name("id").value(it) }
value.val1?.data? let { out.name("data").value(it) }
value.val2?.token? let { out.name("token").value(it) }
value.val2?.url? let { out.name("url").value(it) }
value.val2?.quantity? let { out.name("quantity").value(it) }
out.endObject()
} else {
out.nullValue()
}
}
@Throws(IOException::class)
override fun read(in: JsonReader): ApiResponse {
reader.beginObject()
var id: String? = null
var data: String? = null
var token: String? = null
var url: String? = null
var quantity: Int = 0
while(in.hasNext()) {
val name = in.nextName()
if (name.equals("id", true)) {
id = in.nextString()
} else if (name.equals("data", true)) {
data = in.nextString()
} else if (name.equals("token", true)) {
token = in.nextString()
} else if (name.equals("url", true)) {
url = in.nextString()
} else if (name.equals("quantity", true)) {
quantity = in.nextInt()
}
}
reader.endObject()
if (id != null && data != null) {
return ApiResponse(MyClass1(id, data), null)
} else if (token != null && url != null) {
return ApiResponse(null, MyClass2(token, url, quantity))
} else {
return ApiResponse()
}
}
}
Then you can add this type adapter to your Gson instance: 然后,您可以将此类型适配器添加到您的Gson实例:
val gson = GsonBuilder().registerTypeAdapter(ApiResponse::class.java, ApiResponseAdapter()).create()
Then replace the Call<Any>
type with Call<ApiRepsone>
and you can then check which response was received by checking which value is null
: 然后用Call<ApiRepsone>
替换Call<Any>
类型,然后通过检查哪个值为null
来检查收到的响应:
if (response.body().val1 != null) {
// Handle MyClass1 response...
} else if (response.body().val2 != null) {
// Handle MyClass2 response...
}
First of all, thanks @Bryan for answer. 首先,感谢@Bryan的回答。 Your answer was perfect but finally I did something tricky way. 你的答案是完美的,但最后我做了一些棘手的事情。
...
if (response.isSuccessful) {
val jsonObject = JSONObject(response.body() as Map<*, *>)
val jsonString = jsonObject.toString()
if (jsonObject.has("id")) {
val myclass1Object = Gson().fromJson(jsonString, MyClass1::class.java)
...
} else {
val myclass2Object = Gson().fromJson(jsonString, MyClass2::class.java)
...
}
}
...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.