使用 moshi 和 retrofit2 解析 JSON 个字符串

Parsing JSON Strings with moshi and retrofit2

我正在使用的 API 响应一个嵌套在列表中的 JSON 对象,如下所示:

[
    {
        "name": "Seattle",
        "lat": 47.6038321,
        "lon": -122.3300624,
        "country": "US",
        "state": "Washington"
    }
]

我想用 Moshi 将 JSON 解析为以下内容 class:

package com.example.weatherapp.entity

// the main JSON response
data class LocationWeather(val name: String, val lat: Float, val lon: Float)

我的API服务文件如下

package com.example.weatherapp.network

import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.http.GET
import com.example.weatherapp.entity.LocationWeather
import retrofit2.http.Query

private const val BASE_URL = "http://api.openweathermap.org/geo/1.0/"


private val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

private val retrofit = Retrofit.Builder()
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .baseUrl(BASE_URL)
    .build()


interface WeatherAPIService {
    @GET("direct?")
    fun getWeatherFromAPI(@Query("q") loc: String,
                          @Query("limit") lim: Int,
                          @Query("appid") key: String): Call<LocationWeather>
}

object WeatherApi {
    val retrofitService : WeatherAPIService by lazy {
        retrofit.create(WeatherAPIService::class.java)
    }
}

我的ViewModel,实际连接到API,如下:

package com.example.weatherapp.overview


import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.weatherapp.entity.LocationWeather
import com.example.weatherapp.network.WeatherApi
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class OverviewViewModel : ViewModel() {

    // the mutable backing property
    private var _response = MutableLiveData<String>()

    // the immutable exposed property
    val response: LiveData<String>
        get() = _response



    fun getWeather(location: String) {
        WeatherApi.retrofitService.getWeatherFromAPI(location, 1, "API_KEY_REDACTED").enqueue(
            object: Callback<LocationWeather> {
                override fun onResponse(call: Call<LocationWeather>, response: Response<LocationWeather>) {
                    _response.value = response.body()?.name
                }

                override fun onFailure(call: Call<LocationWeather>, t: Throwable) {
                    _response.value = "Failure: " + t.message
                }
            }
        )
    }

}

与此 ViewModel 关联的片段仅观察响应 LiveData 并将值呈现给 TextView。呈现的值是“失败:预期 BEGIN_OBJECT 但在路径 $ 处是 BEGIN_ARRAY”。我认为问题在于,由于 JSON 嵌套在列表中,因此值未正确存储在 LocationWeather 数据 class 中。否则代码编译并且模拟器运行。尝试将同一代码与未嵌套在列表中的不同 API 一起使用完全按预期工作(前提是我更新了 LocationWeather 中的参数名称)。然而,当被迫解析上面的 JSON 时,我不知所措。我阅读了 moshi 文档和其他几篇文章,但我不完全确定如何使用适配器或 Types.newParameterizedType() 方法实现他们的解决方案。

如何使用 moshi 和 retrofit2 解析此 JSON?

示例响应数据为数组。

[
    {
        "name": "Seattle",
        "lat": 47.6038321,
        "lon": -122.3300624,
        "country": "US",
        "state": "Washington"
    }
]

[] <- 这是数组。

更改响应类型。 这是例子。

interface WeatherAPIService {
    @GET("direct?")
    fun getWeatherFromAPI(@Query("q") loc: String,
                          @Query("limit") lim: Int,
                          @Query("appid") key: String): Call<List<LocationWeather>>
}

您的 API 正在 return 获取 LocationWeather 对象列表,但您的代码试图获取单个 LocationWeather 对象。因此,它抛出了上述异常。

更新您的代码以解决问题:

 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>>) {
                    // here I'm trying to access the first element of the list using 0th index.
                    _response.value = response.body()[0]?.name
                }

                override fun onFailure(call: Call<LocationWeather>, t: Throwable) {
                    _response.value = "Failure: " + t.message
                }
            }
        )
    }

您还必须更新接口中方法的 return 类型:

interface WeatherAPIService {
    @GET("direct?")
    fun getWeatherFromAPI(@Query("q") loc: String,
                          @Query("limit") lim: Int,
                          @Query("appid") key: String): Call<List<LocationWeather>>
}