You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中Retrofit多类型响应的解析崩溃处理方案

Handling Multi-Type Responses in Retrofit (Kotlin)

Great question—this is a super common gotcha when dealing with APIs that don’t stick to a single response format. The crash happens because Retrofit tries to parse the response strictly against the Call<List<Type>> you defined, but gets a JSON object instead of an array. Here are two solid ways to fix this in Kotlin:

Option 1: Manually Parse ResponseBody (Quick & Simple)

The easiest approach is to first fetch the raw ResponseBody, then manually check the response format and parse accordingly. This is perfect for one-off cases where you don’t want to overhaul your Retrofit setup.

Step 1: Update Your Interface

Change the return type to Call<ResponseBody> so you can access the raw JSON string:

@POST("user/userTimetable")
fun getTypeApi(
    @HeaderMap headers: Map<String, String>,
    @Body body: ScheduleBody?
): Call<ResponseBody>

Step 2: Parse the Response in Callback

In your enqueue callback, check if the response is an array or object, then parse each case:

val gson = Gson()
val call = apiService.getTypeApi(headers, body)

call.enqueue(object : Callback<ResponseBody> {
    override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
        if (response.isSuccessful) {
            response.body()?.string()?.let { jsonString ->
                // First, check if the JSON is an array
                val jsonElement = gson.fromJson(jsonString, JsonElement::class.java)
                if (jsonElement.isJsonArray) {
                    // Parse as your list of Type objects
                    val typeList = gson.fromJson(jsonString, object : TypeToken<List<Type>>() {}.type)
                    handleSuccess(typeList)
                } else if (jsonElement.isJsonObject) {
                    // Parse as the error response
                    val errorResponse = gson.fromJson(jsonString, ErrorResponse::class.java)
                    handleError(errorResponse.message)
                } else {
                    handleError("Invalid response format")
                }
            } ?: handleError("Empty response body")
        } else {
            handleError("Request failed with code: ${response.code()}")
        }
    }

    override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
        handleError(t.message ?: "Unknown network error")
    }
})

// Define your helper data classes
data class Type(val type: String, val color: String)
data class ErrorResponse(val message: String)

// Example handler functions
fun handleSuccess(data: List<Type>) { /* Process your list */ }
fun handleError(message: String) { /* Show error to user */ }

Option 2: Sealed Class + Custom Gson Converter (Clean & Scalable)

For a more maintainable solution (especially if multiple APIs have this behavior), use a sealed class to represent both success and error responses, then create a custom Gson deserializer to handle the parsing automatically.

Step 1: Define a Sealed Class for Responses

This class will encapsulate both possible response types:

sealed class ApiResult {
    data class Success(val data: List<Type>) : ApiResult()
    data class Error(val message: String) : ApiResult()
}

Step 2: Create a Custom JsonDeserializer

This deserializer will check the response type and map it to the appropriate sealed class variant:

class ApiResultDeserializer : JsonDeserializer<ApiResult> {
    override fun deserialize(
        json: JsonElement?,
        typeOfT: Type?,
        context: JsonDeserializationContext?
    ): ApiResult {
        json ?: return ApiResult.Error("Empty response")

        return when {
            json.isJsonArray -> {
                val typeList = context?.deserialize<List<Type>>(
                    json,
                    object : TypeToken<List<Type>>() {}.type
                ) ?: emptyList()
                ApiResult.Success(typeList)
            }
            json.isJsonObject -> {
                val errorResponse = context?.deserialize<ErrorResponse>(json, ErrorResponse::class.java)
                ApiResult.Error(errorResponse?.message ?: "Unknown error")
            }
            else -> ApiResult.Error("Invalid response format")
        }
    }
}

Step 3: Configure Retrofit with the Custom Converter

Add the deserializer to your Gson instance when building Retrofit:

val customGson = GsonBuilder()
    .registerTypeAdapter(ApiResult::class.java, ApiResultDeserializer())
    .create()

val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create(customGson))
    .build()

Step 4: Update Your Interface & Use the Sealed Class

Now your interface can return Call<ApiResult>, and you can handle the response cleanly with a when expression:

@POST("user/userTimetable")
fun getTypeApi(
    @HeaderMap headers: Map<String, String>,
    @Body body: ScheduleBody?
): Call<ApiResult>

// Usage
val call = apiService.getTypeApi(headers, body)
call.enqueue(object : Callback<ApiResult> {
    override fun onResponse(call: Call<ApiResult>, response: Response<ApiResult>) {
        if (response.isSuccessful) {
            when (val result = response.body()) {
                is ApiResult.Success -> handleSuccess(result.data)
                is ApiResult.Error -> handleError(result.message)
            }
        } else {
            handleError("Request failed with code: ${response.code()}")
        }
    }

    override fun onFailure(call: Call<ApiResult>, t: Throwable) {
        handleError(t.message ?: "Unknown network error")
    }
})

Key Notes

  • Avoid using try-catch for parsing checks if possible—checking isJsonArray()/isJsonObject() is more efficient and intentional.
  • The sealed class approach keeps your code clean and makes it easy to extend if you need to handle more response types later.

内容的提问来源于stack exchange,提问作者Ammar Ahsan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 01:37:47