Android:Kotlin - Retrofit2 - Moshi |处理复杂的 json
Android: Kotlin - Retrofit2 - Moshi | working with complex json
我正在尝试使用此 API 请求获取游戏列表。
https://steamspy.com/api.php?request=top100forever
但是我认为我的问题是所有游戏都在另一个东西里面,只有 appid。
这是响应的样子
{
"10": {
"appid": 10,
"name": "Counter-Strike",
"developer": "Valve",
"publisher": "Valve",
"score_rank": "",
"positive": 143404,
"negative": 3739,
"userscore": 0,
"owners": "20,000,000 .. 50,000,000",
"average_forever": 9671,
"average_2weeks": 815,
"median_forever": 287,
"median_2weeks": 925,
"price": "99",
"initialprice": "999",
"discount": "90"
},
"30": {
"appid": 30,
"name": "Day of Defeat",
"developer": "Valve",
"publisher": "Valve",
"score_rank": "",
"positive": 3885,
"negative": 463,
"userscore": 0,
"owners": "5,000,000 .. 10,000,000",
"average_forever": 259,
"average_2weeks": 0,
"median_forever": 33,
"median_2weeks": 0,
"price": "49",
"initialprice": "499",
"discount": "90"
},
....
}
我的 apiService:
private const val BASE_URL_GAMES = "https://steamspy.com/"
private val moshi = Moshi.Builder()
//.add(ResponseGetGamesAdapter())
.add(KotlinJsonAdapterFactory())
.build()
//OkhttpClient for building http request url
private val tmdbClient = OkHttpClient().newBuilder()
.build()
private val retrofit = Retrofit.Builder()
.client(tmdbClient)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.baseUrl(BASE_URL_GAMES)
.build()
interface GameApiService{
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<List<Game>>
}
object GameApi {
val retrofitService: GameApiService by lazy {
retrofit.create(GameApiService::class.java)
}
}
视图模型
enum class GameApiStatus { LOADING, ERROR, DONE }
class GameOverviewViewModel : ViewModel() {
// TODO: Implement the
private val _response = MutableLiveData<String>()
private val _status = MutableLiveData<GameApiStatus>()
//private var gameRepository: GameRepository = GameRepository()
// The external immutable LiveData for the response String
val response: LiveData<GameApiStatus>
get() = _status
private val _properties = MutableLiveData<List<Game>>()
val properties: LiveData<List<Game>>
get() = _properties
// Create a Coroutine scope using a job to be able to cancel when needed
private var viewModelJob = Job()
// the Coroutine runs using the Main (UI) dispatcher
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main )
init {
getTop100()
}
private fun getTop100() {
coroutineScope.launch {
// Get the Deferred object for our Retrofit request
var getPropertiesDeferred = GameApi.retrofitService.getTop100()
try {
// Await the completion of our Retrofit request
var listResult = getPropertiesDeferred.await()
_status.value = GameApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
val error = e.message
_status.value = GameApiStatus.ERROR
_properties.value = ArrayList()
}
}
}
/**
* When the [ViewModel] is finished, we cancel our coroutine [viewModelJob], which tells the
* Retrofit service to stop.
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
游戏模型
data class Game(
//var id:Int,
@field:Json(name = "appid")
var appid:Int,
@field:Json(name = "name")
var name:String
//var desc:String
)
在这一点上,我已经苦苦挣扎了这么久,我可能在途中弄坏了更多东西哈哈
我假设我需要某种适配器,但我绝对一无所知。
试图尽可能接近我们课程中的完成方式,但此时我将采取任何工作方式:D
看来问题出在这里:
interface GameApiService{
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<List<Game>>
}
你在你的代码中说你会得到一个 Game
对象的 List
,但是你的 api 没有给你那个。
这是您的回复api:
{
"10": {
"appid": 10,
...
},
"30": {
"appid": 30,
...
}
}
在此示例中,您的 API 返回的是一个对象 10
,其中包含许多其他数据,以及一个对象 30
,其中包含许多其他数据。
似乎 10
和 30
包含相同的字段,因此可以帮助您。
这意味着你可以创建一个新数据class,可以调用GameServiceResponse
。
data class GameServiceResponse(
@Json(name = "10") val 10: Game,
@Json(name = "30") val 30: Game
)
然后您更改 GameApiService
以接受此回复。但是,您一开始就没有收到 List
,所以必须取消。
interface GameApiService{
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<GameServiceResponse>
}
最后,当你食用它时,你会得到类似 getPropertiesDeferred.10.appid
的东西
还有一件事:由于您使用的是 moshi,我相信您仍然需要告诉 api class 您正在使用 moshi 进行反序列化(如果您的适配器是做这部分):
interface GameApiService{
@MoshiDeserialization
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<GameServiceResponse>
}
我希望这是有道理的。
尝试如下更新:
更新服务
interface GameApiService{
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<Map<String,Game>>
}
然后你会从API得到地图的结果。
我正在尝试使用此 API 请求获取游戏列表。 https://steamspy.com/api.php?request=top100forever
但是我认为我的问题是所有游戏都在另一个东西里面,只有 appid。 这是响应的样子
{
"10": {
"appid": 10,
"name": "Counter-Strike",
"developer": "Valve",
"publisher": "Valve",
"score_rank": "",
"positive": 143404,
"negative": 3739,
"userscore": 0,
"owners": "20,000,000 .. 50,000,000",
"average_forever": 9671,
"average_2weeks": 815,
"median_forever": 287,
"median_2weeks": 925,
"price": "99",
"initialprice": "999",
"discount": "90"
},
"30": {
"appid": 30,
"name": "Day of Defeat",
"developer": "Valve",
"publisher": "Valve",
"score_rank": "",
"positive": 3885,
"negative": 463,
"userscore": 0,
"owners": "5,000,000 .. 10,000,000",
"average_forever": 259,
"average_2weeks": 0,
"median_forever": 33,
"median_2weeks": 0,
"price": "49",
"initialprice": "499",
"discount": "90"
},
....
}
我的 apiService:
private const val BASE_URL_GAMES = "https://steamspy.com/"
private val moshi = Moshi.Builder()
//.add(ResponseGetGamesAdapter())
.add(KotlinJsonAdapterFactory())
.build()
//OkhttpClient for building http request url
private val tmdbClient = OkHttpClient().newBuilder()
.build()
private val retrofit = Retrofit.Builder()
.client(tmdbClient)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.baseUrl(BASE_URL_GAMES)
.build()
interface GameApiService{
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<List<Game>>
}
object GameApi {
val retrofitService: GameApiService by lazy {
retrofit.create(GameApiService::class.java)
}
}
视图模型
enum class GameApiStatus { LOADING, ERROR, DONE }
class GameOverviewViewModel : ViewModel() {
// TODO: Implement the
private val _response = MutableLiveData<String>()
private val _status = MutableLiveData<GameApiStatus>()
//private var gameRepository: GameRepository = GameRepository()
// The external immutable LiveData for the response String
val response: LiveData<GameApiStatus>
get() = _status
private val _properties = MutableLiveData<List<Game>>()
val properties: LiveData<List<Game>>
get() = _properties
// Create a Coroutine scope using a job to be able to cancel when needed
private var viewModelJob = Job()
// the Coroutine runs using the Main (UI) dispatcher
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main )
init {
getTop100()
}
private fun getTop100() {
coroutineScope.launch {
// Get the Deferred object for our Retrofit request
var getPropertiesDeferred = GameApi.retrofitService.getTop100()
try {
// Await the completion of our Retrofit request
var listResult = getPropertiesDeferred.await()
_status.value = GameApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
val error = e.message
_status.value = GameApiStatus.ERROR
_properties.value = ArrayList()
}
}
}
/**
* When the [ViewModel] is finished, we cancel our coroutine [viewModelJob], which tells the
* Retrofit service to stop.
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
游戏模型
data class Game(
//var id:Int,
@field:Json(name = "appid")
var appid:Int,
@field:Json(name = "name")
var name:String
//var desc:String
)
在这一点上,我已经苦苦挣扎了这么久,我可能在途中弄坏了更多东西哈哈 我假设我需要某种适配器,但我绝对一无所知。 试图尽可能接近我们课程中的完成方式,但此时我将采取任何工作方式:D
看来问题出在这里:
interface GameApiService{
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<List<Game>>
}
你在你的代码中说你会得到一个 Game
对象的 List
,但是你的 api 没有给你那个。
这是您的回复api:
{
"10": {
"appid": 10,
...
},
"30": {
"appid": 30,
...
}
}
在此示例中,您的 API 返回的是一个对象 10
,其中包含许多其他数据,以及一个对象 30
,其中包含许多其他数据。
似乎 10
和 30
包含相同的字段,因此可以帮助您。
这意味着你可以创建一个新数据class,可以调用GameServiceResponse
。
data class GameServiceResponse(
@Json(name = "10") val 10: Game,
@Json(name = "30") val 30: Game
)
然后您更改 GameApiService
以接受此回复。但是,您一开始就没有收到 List
,所以必须取消。
interface GameApiService{
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<GameServiceResponse>
}
最后,当你食用它时,你会得到类似 getPropertiesDeferred.10.appid
还有一件事:由于您使用的是 moshi,我相信您仍然需要告诉 api class 您正在使用 moshi 进行反序列化(如果您的适配器是做这部分):
interface GameApiService{
@MoshiDeserialization
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<GameServiceResponse>
}
我希望这是有道理的。
尝试如下更新: 更新服务
interface GameApiService{
@GET("api.php?request=top100forever")
fun getTop100(): Deferred<Map<String,Game>>
}
然后你会从API得到地图的结果。