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

Retrofit Gson反序列化枚举异常:rights字段返回空列表

Retrofit解析SimpleResponse<T>时rights字段为空列表的排查与修复

问题场景

服务器返回JSON:

{"rights":[{"name":"stock_management","right":true}]}

使用Retrofit的SimpleResponse<T>类转换响应时,rights字段始终得到空列表,但直接用Gson实例解析、Retrofit拦截器内解析该JSON均正常。

相关代码

数据类定义

data class SimpleResponse<T>(
    val data: T? = null,
    val error: String? = null,
    val rights: List<LMSPermission> = emptyList()
)

@Entity
data class LMSPermission(
    @PrimaryKey(autoGenerate = false)
    val name: Permission,
    val right: Boolean = false
) {
    enum class Permission(val lmsName: String) {
        @SerializedName("stock_management")
        STOCK_PERMISSION("stock_management")
    }
}

Retrofit配置代码

@Provides
@Singleton
fun provideRetrofitDAO(): RetrofitDao {
    val gson = GsonBuilder()
        .registerTypeAdapter(Event::class.java, EventDeserializer())

    val logging = HttpLoggingInterceptor()
        .setLevel(HttpLoggingInterceptor.Level.BODY)
    val httpClient = OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .addInterceptor(logging)
        .addInterceptor { chain ->
            val response = chain.proceed(chain.request().newBuilder().build())
            Log.d(TAG, "provideRetrofitDAO: $response")
            response.let {
                if (it.code == 200)
                    Log.d(TAG, "provideRetrofitDAO: ${gson.create().fromJson<SimpleResponse<Any>>(it.body.string(), TypeToken.getParameterized(SimpleResponse::class.java, Any::class.java).type)}")
            }
            response
        }
    val retrofitBuilder =
        Retrofit
            .Builder()
            .baseUrl(if (MainActivity.debug) API_BASE_URL_DEBUG else API_BASE_URL)
            .addConverterFactory(
                GsonConverterFactory.create(gson.create())
            )
            .callbackExecutor(Executors.newSingleThreadExecutor())
    val retrofit =
        retrofitBuilder
            .client(
                httpClient.build()
            )
            .build()

    return retrofit.create(RetrofitDao::class.java)
}

问题原因

  1. 枚举反序列化失败:LMSPermission的name字段是Permission枚举,Gson默认无法将JSON中的"stock_management"字符串映射到枚举值STOCK_PERMISSION——你添加的@SerializedName注解无法被Gson默认的枚举解析逻辑识别,导致单个LMSPermission实例解析失败,最终整个rights列表被解析为空。
  2. Gson实例复用问题:代码中多次调用gson.create()生成新的Gson实例,虽然拦截器内的实例能正常解析(可能是巧合或日志输出的误导),但Retrofit使用的实例并未正确处理枚举解析。

修复方案

方案1:给枚举添加自定义反序列化器

创建枚举的反序列化适配器,让Gson能根据lmsName匹配枚举值:

class PermissionDeserializer : JsonDeserializer<LMSPermission.Permission> {
    override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): LMSPermission.Permission {
        val permissionStr = json.asString
        return LMSPermission.Permission.values().firstOrNull { it.lmsName == permissionStr }
            ?: throw JsonParseException("Unknown permission: $permissionStr")
    }
}

然后在GsonBuilder中注册该适配器,只创建一次Gson实例:

val gson = GsonBuilder()
    .registerTypeAdapter(Event::class.java, EventDeserializer())
    .registerTypeAdapter(LMSPermission.Permission::class.java, PermissionDeserializer())
    .create() // 仅调用一次create(),复用该实例

修改Retrofit配置中GsonConverterFactory的参数:

.addConverterFactory(GsonConverterFactory.create(gson))

同时删除拦截器内的gson.create(),直接使用已创建的gson实例。

方案2:临时修改字段类型规避枚举解析问题

将LMSPermission的name字段改为String类型,在业务代码中手动转换为枚举:

@Entity
data class LMSPermission(
    @PrimaryKey(autoGenerate = false)
    val name: String,
    val right: Boolean = false
) {
    fun toPermission(): Permission {
        return Permission.values().firstOrNull { it.lmsName == name }
            ?: throw IllegalArgumentException("Unknown permission: $name")
    }

    enum class Permission(val lmsName: String) {
        STOCK_PERMISSION("stock_management")
    }
}

这种方式无需配置Gson适配器,快速规避枚举解析的坑。

方案3:检查Retrofit接口定义

确保接口返回类型与解析类匹配,例如:

interface RetrofitDao {
    @GET("your-api-path")
    suspend fun fetchData(): SimpleResponse<YourTargetDataType>
}

错误的返回类型会导致Gson解析逻辑异常。

额外排查步骤

  • 开启Gson调试日志,查看解析过程中的错误信息:
val gson = GsonBuilder()
    .setLogLevel(LogLevel.VERBOSE)
    .registerTypeAdapter(...)
    .create()
  • 查看HttpLoggingInterceptor的输出,确认服务器返回的JSON与预期完全一致。

内容的提问来源于stack exchange,提问作者Arek Kubiński

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 20:04:55