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

Retrofit+Gson解析API返回200状态码但response.body全为null

Retrofit+Gson 接口200但response.body()全字段为null排查方案

排查步骤(按问题出现概率从高到低排序)

  • 最高优先级:检查OkHttp自定义拦截器的响应读取逻辑
    如果你是自己实现拦截器打印响应日志,大概率是消费了响应流但没有重新构建响应传给下游:OkHttp的ResponseBody是单向流,内容只能被读取一次,如果你直接调用response.body()?.string()打印日志,读完流就会关闭,后续Retrofit的Gson转换器再读取时拿到的是空内容,解析出来自然全字段为null,且不会抛出明确异常。
    修复方案:

    1. 打印日志时改用response.peekBody(Long.MAX_VALUE).string()读取内容,peekBody不会消费原始响应流,不影响后续解析流程;
    2. 如果已经调用了string()方法消费了流,必须重新构建Response对象,把读取到的字符串重新包装成ResponseBody塞回响应中再返回给链式调用的下一环。
      官方提供的HttpLoggingInterceptor已经做了流复用处理,直接用官方依赖不会触发这个问题,自定义拦截器非常容易踩这个坑。
  • 第二优先级:核对响应JSON结构和实体类层级、字段名的匹配度
    这类问题占剩下场景的90%:

    1. 先把拦截器打印的完整JSON复制出来,逐层级和你定义的LoginResponse类比对:比如你定义的LoginResponse直接对应id、faunadb_token、data三个字段,但实际接口最外层多了一层通用包装(例如结构为{"code":200,"msg":"success","data":{"id":"xxx","faunadb_token":"xxx","user_detail":{...}}}),Gson匹配不到对应字段时会直接给字段赋值null,不会主动抛错。
    2. 字段名严格匹配:如果接口返回的是下划线命名(如faunadb_token),实体类写字段名要么和JSON完全一致,要么给驼峰命名的字段加@SerializedName("对应JSON的key")注解,要么初始化Gson时配置setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)开启自动驼峰转下划线映射。注意拼写错误(比如少打字母、后缀写错)也会导致匹配失败。
    3. 字段类型要匹配:比如接口返回的id是字符串类型,你实体类定义成Int/Long,Gson类型转换失败时也可能赋值null,建议给Gson加上setLenient()开启宽松模式,避免类型不匹配直接解析失败。
  • 第三优先级:检查Retrofit的Gson转换器配置是否正确
    常见配置错误:

    1. 没有添加GsonConverterFactory,或者添加顺序错误:Retrofit默认只能将响应解析为ResponseBody类型,必须在构建Retrofit实例时调用addConverterFactory(GsonConverterFactory.create(你自定义配置的Gson实例)),如果在Gson转换器之前添加了ScalarsConverterFactory等其他转换器,可能导致响应被提前转换为字符串,无法正常序列化为实体类。
      正确配置参考:
    val customGson = GsonBuilder()
        .setLenient()
        // 接口下划线转驼峰才需要加这行,字段名完全匹配就不用
        .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
        .create()
    
    val retrofit = Retrofit.Builder()
        .baseUrl(ApiConfig.BASE_URL)
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create(customGson))
        .build()
    
    1. ApiService接口定义的泛型错误:比如返回值写成Call<Response<LoginResponse>>嵌套了两层Retrofit的Response,或者协程接口写错返回值类型,都会导致解析逻辑异常。
  • 第四优先级:检查Kotlin实体类和混淆规则

    1. 响应实体类不要写成其他类的非静态内部类(Kotlin里普通内部类默认持有外部类引用,Gson反射实例化时会失败),建议把所有响应实体类单独放在model/response包下,定义为顶层类。
    2. 实体类字段建议给默认值,避免Gson反射赋值时因为构造函数参数问题失败,正确写法参考:
    data class LoginResponse(
        @SerializedName("user_id")
        val userId: String = "",
        @SerializedName("faunadb_token")
        val faunadbToken: String = "",
        @SerializedName("data")
        val userInfo: UserDetail = UserDetail()
    )
    
    data class UserDetail(
        val nickname: String = "",
        val avatar: String = ""
    )
    
    1. 如果是release包出现该问题,debug包正常,优先检查混淆规则:需要在proguard配置中保留所有响应实体类的字段不被混淆,否则混淆后字段名变成a/b/c,无法和JSON key匹配。添加规则:
    -keepattributes Signature
    -keepattributes *Annotation*
    -keep class 你自己的包名.model.** { <fields>; }
    -dontwarn sun.misc.**
    

快速定位技巧

如果以上步骤排查完还没找到问题,直接在拦截器中拿到响应字符串后,手动用Gson做一次解析,catch住异常打印日志,Gson会明确告诉你是哪个字段、哪一层级解析失败:

val responseStr = response.peekBody(Long.MAX_VALUE).string()
try {
    val testParse = customGson.fromJson(responseStr, LoginResponse::class.java)
    Log.d("GSON_PARSE_TEST", "解析结果:$testParse")
} catch (e: Exception) {
    Log.e("GSON_PARSE_TEST", "解析失败详情", e)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:15:45