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

Retrofit中如何处理同一字段返回不同数据类型的问题(OtpVerifyModel场景)

Hey there, let's tackle this tricky API response issue you're facing—having a single data field that switches between an object and a string depending on the result code is definitely a pain, but we've got solid fixes for it! Here are a few approaches, ordered from quick-and-dirty to type-safe and maintainable:

1. Quick Temporary Fix: Use Any for the data Field

If you need a fast solution without too much refactoring, you can change the data field's type to Any? and handle casting manually after receiving the response.

First, update your data class:

data class OtpVerifyModel(
    val result: Int? = null,
    val msg: String? = null,
    val data: Any? = null // Switch to Any type
)

Then, when processing the Retrofit response:

val response = apiService.verifyOtp(...)
if (response.isSuccessful) {
    response.body()?.let { model ->
        when (model.result) {
            1 -> {
                // Safely cast to Datas object
                val successData = model.data as? Datas
                successData?.let {
                    // Handle your successful data logic here
                }
            }
            -1 -> {
                // Safely cast to error string
                val errorMessage = model.data as? String
                errorMessage?.let {
                    // Handle error message display/logic here
                }
            }
        }
    }
}

Pros: Fast to implement, minimal code changes.
Cons: Not type-safe—risk of ClassCastException if the API returns unexpected types, and IDE won't help with autocompletion for the data field.

2. Type-Safe Best Practice: Custom Gson TypeAdapter + Sealed Class

This is the most elegant and maintainable solution for Kotlin, leveraging sealed classes to represent the two possible states of data, plus a custom Gson adapter to handle automatic parsing.

Step 1: Define a Sealed Class for data

Sealed classes let you represent restricted class hierarchies, perfect for cases where data can only be one of two types:

sealed class OtpData {
    data class Success(val datas: Datas) : OtpData()
    data class Error(val message: String) : OtpData()
    object Empty : OtpData() // Handle null/unexpected cases
}

Step 2: Update Your Main Data Class

Modify OtpVerifyModel to use the sealed class:

data class OtpVerifyModel(
    val result: Int? = null,
    val msg: String? = null,
    val data: OtpData? = null
)

Step 3: Create a Custom Gson TypeAdapter

This adapter tells Gson how to parse the data field based on its JSON type:

class OtpDataAdapter : TypeAdapter<OtpData>() {
    private val gson = Gson()
    private val datasType = object : TypeToken<Datas>() {}.type

    override fun write(out: JsonWriter, value: OtpData?) {
        // We only care about deserialization here—skip serialization for now
        throw UnsupportedOperationException("Serialization not implemented")
    }

    override fun read(`in`: JsonReader): OtpData {
        return when (`in`.peek()) {
            JsonToken.STRING -> {
                // Parse as error message string
                OtpData.Error(`in`.nextString())
            }
            JsonToken.BEGIN_OBJECT -> {
                // Parse as Datas object
                OtpData.Success(gson.fromJson(`in`, datasType))
            }
            JsonToken.NULL -> {
                `in`.nextNull()
                OtpData.Empty
            }
            else -> {
                // Skip unexpected types to avoid crashes
                `in`.skipValue()
                OtpData.Empty
            }
        }
    }
}

Step 4: Register the Adapter with Retrofit

When building your Retrofit instance, add the custom adapter to Gson:

val customGson = GsonBuilder()
    .registerTypeAdapter(OtpData::class.java, OtpDataAdapter())
    .create()

val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL) // Replace with your base URL
    .addConverterFactory(GsonConverterFactory.create(customGson))
    .build()

Step 5: Handle the Response Safely

Now you can use Kotlin's when expression with full type safety:

val response = apiService.verifyOtp(...)
if (response.isSuccessful) {
    response.body()?.let { model ->
        when (model.data) {
            is OtpData.Success -> {
                val successDatas = model.data.datas
                // Handle successful data logic
            }
            is OtpData.Error -> {
                val errorMsg = model.data.message
                // Handle error message logic
            }
            OtpData.Empty -> {
                // Handle null or unexpected data cases
            }
        }
    }
}

Pros: 100% type-safe, no casting needed, IDE autocompletion works, and code is clean and maintainable.
Cons: Requires a bit more setup upfront, but it's worth it for long-term code health.

3. Alternative: Parse Response Body Manually

If you don't want to mess with custom adapters, you can have Retrofit return a raw ResponseBody and parse it yourself:

First, update your Retrofit interface:

@FormUrlEncoded
@POST("Api/login")
suspend fun verifyOtp(
    @Query("height") height: String,
    @Query("width") width: String,
    @Field("key") key: String,
    @Field("type") type: String,
    @Field("phone") phone: String,
    @Field("email") email: String,
    @Field("otp") otp: String,
    @Field("country_code") country_code: String,
    @Field("country_flag") country_flag: String
): Response<ResponseBody>

Then parse the response manually:

val response = apiService.verifyOtp(...)
if (response.isSuccessful) {
    response.body()?.string()?.let { jsonString ->
        val jsonObj = JSONObject(jsonString)
        val result = jsonObj.getInt("result")
        
        if (result == 1) {
            val successModel = Gson().fromJson(jsonString, OtpSuccessModel::class.java)
            // Handle successModel.data (Datas type)
        } else if (result == -1) {
            val errorModel = Gson().fromJson(jsonString, OtpErrorModel::class.java)
            // Handle errorModel.data (String type)
        }
    }
}

Where OtpSuccessModel and OtpErrorModel are separate data classes:

data class OtpSuccessModel(
    val result: Int? = null,
    val msg: String? = null,
    val data: Datas? = null
)

data class OtpErrorModel(
    val result: Int? = null,
    val msg: String? = null,
    val data: String? = null
)

Pros: No custom adapters needed.
Cons: More boilerplate code, and you lose the convenience of Retrofit's automatic parsing.


内容的提问来源于stack exchange,提问作者Rahul Pathak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 19:52:50