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

Android Kotlin使用Retrofit2请求API触发Gson类型不匹配解析异常

错误根因

你遇到的JsonSyntaxException: Expected string but was BEGIN_OBJECT核心是3类问题:

  • 实体类字段类型与API返回结构不匹配:address、company在接口返回中是嵌套对象,你在QuoteModel中定义为String类型,Gson解析时类型不匹配直接抛出异常
  • Retrofit接口定义不规范:单用户查询路径格式错误,缺少路径参数注解
  • 业务逻辑存在多处错误:id取值错误、查询用例未触发网络请求、异步方法返回值处理错误

具体修改方案

1. 修正QuoteModel实体类,适配API返回的嵌套结构

// 嵌套的地址子实体
data class Address(
    @SerializedName("street") val street: String,
    @SerializedName("suite") val suite: String,
    @SerializedName("city") val city: String,
    @SerializedName("zipcode") val zipcode: String,
    @SerializedName("geo") val geo: Geo
)

// 嵌套的地理位置子实体
data class Geo(
    @SerializedName("lat") val lat: String,
    @SerializedName("lng") val lng: String
)

// 嵌套的公司子实体
data class Company(
    @SerializedName("name") val name: String,
    @SerializedName("catchPhrase") val catchPhrase: String,
    @SerializedName("bs") val bs: String
)

// 修正后的主实体类
data class QuoteModel(
    @SerializedName("id") val quote: Number,
    @SerializedName("name") val name: String,
    @SerializedName("email") val email: String,
    @SerializedName("address") val addrees: Address,
    @SerializedName("phone") val phone: String,
    @SerializedName("website") val website: String,
    @SerializedName("company") val company: Company,
)

如果需要在UI上展示地址/公司信息,直接取嵌套字段即可,比如it.addrees.city、it.company.name。

2. 修正Retrofit请求接口QuoteApiClient

interface QuoteApiClient {
    @GET("/users")
    suspend fun getAllUUsers(): Response<List<QuoteModel>>

    // 修正路径格式,添加@Path注解绑定路径参数
    @GET("/users/{id}")
    suspend fun getUserById(@Path("id") id: Int): Response<QuoteModel>
}

jsonplaceholder单用户查询的标准路径是/users/用户id,不需要加问号。

3. 修正GetUserById用例,触发实际网络请求

class GetUserById @Inject constructor(private val repository: QuoteRepository) {
    suspend operator fun invoke(id: Int): QuoteModel? {
        return repository.getUserById(id)
    }
}

你之前的实现只读取了Provider的缓存数据,没有触发网络请求查询指定id的用户。

4. 修正QuoteService的空返回处理

@Singleton
class QuoteService @Inject constructor(private val api:QuoteApiClient) {
    suspend fun getAllUsers(): List<QuoteModel> {
        return withContext(Dispatchers.IO) {
            val response = api.getAllUUsers()
            response.body() ?: emptyList()
        }
    }

    suspend fun getUserById(id:Int): QuoteModel? {
        return withContext(Dispatchers.IO){
            val response = api.getUserById(id)
            // 空返回直接返回null,不要强转避免崩溃
            response.body()
        }
    }
}

注意同步修改Repository、Provider中对应方法的参数类型,把Number统一改为Int,避免类型转换错误。

5. 修正MainActivity的点击事件逻辑

binding.viewContainer.setOnClickListener {
    // 取EditText的输入内容,不是控件的id,加判空避免非法输入
    val inputId = Etid.text.toString().toIntOrNull()
    inputId?.let { id ->
        quoteViewModel.getUserIdQuote(id)
    }
    // 不需要手动赋值,你已经观察了quoteModel的LiveData,数据更新会自动刷新UI
}

你之前写的Etid.id取的是EditText控件在布局中的资源id,不是用户输入的内容,完全不符合需求。同时getUserIdQuote是异步无返回值方法,不要做类型强转。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 15:18:03