【问题标题】:Retrofit2 & JSON response array with multiple possible values具有多个可能值的 Retrofit2 和 JSON 响应数组
【发布时间】:2020-08-31 19:09:36
【问题描述】:

好久没在这里写东西了,现在真的需要建议)

我使用 Retrofit2 作为 api 客户端。服务器 API 有一个端点,例如 /api/stats,它接收 JSON 正文 request 并返回 JSON response 为:

data class StatsResult<T>(
    @SerializedName("code")         val code: Int,
    @SerializedName("message")      val msg: String?,
    @SerializedName("request_id")   val requestId: String?,
    @SerializedName("data")         val data: T?
)

如果出现错误,data 为空。 否则,data 是一个数组,可以包含不同类型的数据,具体取决于 request 的类型。

例如:

请求1:

{
   "type": "type1",
   "params": {
   }
}

Response:
{
    "code": 0,
    "request_id": "...",
    "data": [
        {
            "key1": "value1",
            "key2": "value2"
        },
        {
            "key1": "value3",
            "key2": "value4"
        }
    ]
}

请求2:

{
   "type": "type2",
   "params": {
   }
}

Response:
{
    "code": 0,
    "request_id": "...",
    "data": [
        {
            "key3": "value1",
            "key4": "value2"
        },
        {
            "key3": "value3",
            "key4": "value4"
        }
    ]
}

简而言之,这是我的实现:

interface StatsApi {
    @POST("/api/stats")
    suspend fun getStats(@Body request: StatsRequest): ApiResponse<StatsData>
}

sealed class ApiResponse<out T: Any> {
    data class Success<T: Any>(val body: T): ApiResponse<T>()
    object Unauthorized : ApiResponse<Nothing>()
    object Forbidden: ApiResponse<Nothing>()
    object NetworkError: ApiResponse<Nothing>()
    data class Error(val msg: String? = null): ApiResponse<Nothing>()
    data class Exception(val t: Throwable): ApiResponse<Nothing>()
}

typealias StatsData = StatsResult<List<BaseStatsDto>>

open class BaseStatsDto()

class Type1StatsDto: BaseStatsDto() {
    @SerializedName("key1") var key1: String? = null
    @SerializedName("key2") var key2: String? = null
}

class Type2StatsDto: BaseStatsDto() {
    @SerializedName("key3") var key3: String? = null
    @SerializedName("key4") var key4: String? = null
}

所以我尝试使用开放/抽象类BaseStatsDto 解决这个问题,然后将其转换为最终类。但是这个解决方案不起作用。

对于响应处理,我使用 CallAdapter.Factory() 和自定义 Call&lt;&gt;

open class ApiResponseCall<T : Any>( 
    private val delegate: Call<T>
) : Call<ApiResponse<T>> {

override fun enqueue(callback: Callback<ApiResponse<T>>) {
    return delegate.enqueue(object : Callback<T> {
        override fun onFailure(call: Call<T>, t: Throwable) {
            val rsp = when (t) {
                is IOException -> ApiResponse.NetworkError
                else -> ApiResponse.Exception(t)
            }
            callback.onResponse(this@ApiResponseCall, Response.success(rsp))
        }

        override fun onResponse(call: Call<T>, response: Response<T>) {
            val rsp: ApiResponse<T>
            rsp = if (response.isSuccessful) {
                val body = response.body()
                ApiResponse.Success(body as T)
            } else {
                val code = response.code()
                val error = response.errorBody()
                when (code) {
                    401 -> ApiResponse.Unauthorized
                    403 -> ApiResponse.Forbidden
                    in 400..499 -> ApiResponse.Error("Client error")
                    in 500..599 -> ApiResponse.Error("Server error")
                    else -> ApiResponse.Exception(Exception("Unknown error"))
                }
            }
            callback.onResponse(this@ApiResponseCall, Response.success(rsp))
        }
    })
}
...
}

我看到了另一种解决方案 - 具有单独的接口函数和单独的响应类型。它工作正常:

@POST("/api/stats")
suspend fun getType1Stats(@Body request: StatsRequest): ApiResponse<StatsResult<List<Type1StatsDto>>>

@POST("/api/stats")
suspend fun getType2Stats(@Body request: StatsRequest): ApiResponse<StatsResult<List<Type2StatsDto>>>

但如果统计数据类型数量增加,维护起来会非常不舒服。

我想要一个统计 API 端点。

【问题讨论】:

    标签: android kotlin retrofit2


    【解决方案1】:

    使用http://www.jsonschema2pojo.org/ 设置SourceType: JSON 和 Annonation style: Gson

    请求 1 的数据

    class Data1 {
    @SerializedName("key1")
    @Expose
    var key1: String? = null
    
    @SerializedName("key2")
    @Expose
    var key2: String? = null
    }
    

    请求1的响应

    class Response1 {
    @SerializedName("code")
    @Expose
    var code: Int? = null
    
    @SerializedName("request_id")
    @Expose
    var requestId: String? = null
    
    @SerializedName("data")
    @Expose
    var data: List<Data1>? = null
    }
    

    请求 2 的数据

     class Data2 {
    @SerializedName("key3")
    @Expose
    var key3: String? = null
    
    @SerializedName("key4")
    @Expose
    var key4: String? = null
    }
    

    请求 2 的响应

    class Response1 {
    @SerializedName("code")
    @Expose
    var code: Int? = null
    
    @SerializedName("request_id")
    @Expose
    var requestId: String? = null
    
    @SerializedName("data")
    @Expose
    var data: List<Data2>? = null
    }
    

    @POST("/api/stats")
    suspend fun getStats1(@Body request: StatsRequest1): ApiResponse<Response1>
    @POST("/api/stats")
    suspend fun getStats2(@Body request: StatsRequest2): ApiResponse<Response2>
    

    【讨论】:

    • 感谢您的回复!正如我在问题结尾处所写,该解决方案正在运行,但我希望为所有统计数据提供一个 api 端点
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-23
    • 1970-01-01
    • 2019-12-14
    • 1970-01-01
    • 2021-12-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多