如何使用Moshi与Retrofit2解析数组包裹的JSON响应
问题根源
报错Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $的原因非常明确:你调用的OpenWeather地理编码接口根结构是JSON数组,但你在Retrofit接口中声明的返回类型是单个LocationWeather对象,Moshi尝试将数组解析为单个实体类时类型不匹配,直接抛出解析失败异常。
解决方案
你可以根据自己的代码习惯选以下任意一种方案,都能正常解析数据。
方案1:直接修改返回类型为列表(无额外代码,推荐)
这是最符合接口实际结构的写法,不需要自定义适配器,只需要修改两处代码即可:
- 修改
WeatherAPIService接口的返回值类型,把单个对象改成列表类型
interface WeatherAPIService { // 注意删掉@GET路径里多余的?,Retrofit会自动拼接查询参数的分隔符,写多余的?会导致请求路径异常 @GET("direct") fun getWeatherFromAPI(@Query("q") loc: String, @Query("limit") lim: Int, @Query("appid") key: String): Call<List<LocationWeather>> }
- 同步修改
OverviewViewModel中网络请求回调的泛型,取列表第一个元素使用即可
fun getWeather(location: String) { WeatherApi.retrofitService.getWeatherFromAPI(location, 1, "API_KEY_REDACTED").enqueue( object: Callback<List<LocationWeather>> { override fun onResponse(call: Call<List<LocationWeather>>, response: Response<List<LocationWeather>>) { // 增加空列表容错,避免接口返回空数组时空指针 _response.value = response.body()?.firstOrNull()?.name ?: "未查询到对应位置" } override fun onFailure(call: Call<List<LocationWeather>>, t: Throwable) { _response.value = "Failure: " + t.message } } ) }
方案2:自定义Moshi适配器,自动提取数组首元素
如果你不想在业务层处理列表逻辑,希望Retrofit直接返回单个LocationWeather对象,可以写一个自定义类型适配器让Moshi自动完成数组到单对象的转换:
- 新建
LocationWeatherAdapter类,定义序列化/反序列化规则
import com.squareup.moshi.FromJson import com.squareup.moshi.ToJson class LocationWeatherAdapter { @FromJson fun fromJson(locationList: List<LocationWeather>): LocationWeather? { // 因为你传了limit=1,接口只会返回1条数据,直接取首元素即可 return locationList.firstOrNull() } @ToJson fun toJson(location: LocationWeather): List<LocationWeather> { return listOf(location) } }
- 把自定义适配器注册到Moshi实例中
private val moshi = Moshi.Builder() .add(LocationWeatherAdapter()) // 自定义适配器要放在Kotlin反射适配器之前注册 .add(KotlinJsonAdapterFactory()) .build()
修改完成后不需要改动原有接口和ViewModel的泛型声明,Moshi会自动完成类型转换。
内容的提问来源于stack exchange,提问作者C-h me
相关产品推荐
相关产品推荐

