【问题标题】:Retrofit 2 response body to custom class将 2 响应体改造为自定义类
【发布时间】:2017-06-22 12:46:49
【问题描述】:

目前,我使用retrofit2 调用restful api并获得响应。因为响应体可以是多种类型,所以我写了如下代码。

//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() 转换为MyClass1MyClass2response.body() as MyClass1 也出现错误。

MyClass1MyClass2 是普通的模板类。

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

有什么聪明的方法可以将响应主体转换为我的自定义类吗?

MyClass2 的小更新

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

【问题讨论】:

  • 为什么不使用自定义对象来接收响应?类似Call&lt;MyResponse&gt;
  • 正如我所描述的,在我的例子中,响应类型可以是多种类型 - MyClass1 或 MyClass2。
  • MyClass1MyClass2)有什么不同吗?
  • 很遗憾,是的。
  • Retrofit 对MyClass1MyClass2 一无所知,您可能想编写一个自定义 responseBodyConverter。

标签: java kotlin retrofit2


【解决方案1】:

正如@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&lt;Any&gt;类型替换为Call&lt;ApiRepsone&gt;,然后您可以通过检查哪个值是null来检查收到了哪个响应:

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

【讨论】:

    【解决方案2】:

    首先,感谢@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)
            ...
        }
    }
    ...
    

    【讨论】:

      猜你喜欢
      • 2016-02-18
      • 1970-01-01
      • 2018-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-04
      • 2016-09-28
      • 1970-01-01
      相关资源
      最近更新 更多