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) }
问题原因
- 枚举反序列化失败:
LMSPermission的name字段是Permission枚举,Gson默认无法将JSON中的"stock_management"字符串映射到枚举值STOCK_PERMISSION——你添加的@SerializedName注解无法被Gson默认的枚举解析逻辑识别,导致单个LMSPermission实例解析失败,最终整个rights列表被解析为空。 - 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
相关产品推荐
相关产品推荐

