将自定义类的响应主体改造2

目前,我使用retrofit2来调用宁静的apis并得到回应。 因为响应体可以是多个类型,所以我写了下面的代码。

//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())) } } } 

所以我不能将response.body() MyClass1 MyClass2MyClass1MyClass2response.body() as MyClass1发生错误。

MyClass1MyClass2是正常的模板类。

 class MyClass1( val id: String, val data: String) 

有没有什么聪明的方法来将响应主体投射到我的自定义类上?

MyClass2的小更新

 class MyClass2( val token: String, val url: String, val quantity: Int) 

正如@ Miha_x64所述,Retrofit不知道你的类( MyClass1MyClass2 ),因为你的Call使用Any类型。 因此,Retrofit不是创建MyClass1MyClass2的实例,而是创建Any类的实例。

最简单的解决办法就是将两个类组合起来:

 data class MyClass( val id: String?, val data: String?, val token: String?, val url: String?, val quantity: Int ) 

然后,您可以在界面中指定响应类型:

 @FormUrlEncoded @POST("payments/events/{id}") fun postPayment(@Path("id") id: String): Call<MyClass> 

如果你的响应没有iddata元素,它们将只是null 。 然后,您可以通过检查哪些值为null来检查接收到哪种类型的响应:

 if (response.body().id != null) { // Handle type 1 response... } else if (response.body().token != null) { // Handle type 2 response... } 

一个稍微复杂一些的解决方案是为两个类编写一个包装器,然后用一个类型适配器来填充包装器。 这将避免每个字段的可空性,以及保持你的数据结构分离。

这会根据您使用的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() } } } 

然后你可以添加这个类型的适配器到你的Gson实例:

 val gson = GsonBuilder().registerTypeAdapter(ApiResponse::class.java, ApiResponseAdapter()).create() 

然后用Call<ApiRepsone>替换Call<Any>类型,然后可以通过检查哪个值为null来检查收到的响应:

 if (response.body().val1 != null) { // Handle MyClass1 response... } else if (response.body().val2 != null) { // Handle MyClass2 response... } 

首先,感谢@Bryan的回答。 你的答案是完美的,但最后我做了一些棘手的方法。

 ... 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) ... } } ...