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
相关产品推荐
相关产品推荐

