【问题标题】:Android: Kotlin - Retrofit2 - Moshi | working with complex jsonAndroid: Kotlin - Retrofit2 - Moshi |处理复杂的 json
【发布时间】:2020-04-18 05:21:22
【问题描述】:

我正在尝试使用此 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"
  },
  ....
}

我的api服务:

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

【问题讨论】:

    标签: android kotlin retrofit2 moshi


    【解决方案1】:

    尝试如下更新: 更新服务

    interface GameApiService{
        @GET("api.php?request=top100forever")
        fun getTop100(): Deferred<Map<String,Game>>
    }
    

    然后你会从 API 中得到 map 的结果。

    【讨论】:

    • 嗯,这是一个简单的解决方法,对我来说很有效,谢谢 :D 你碰巧没有一个同样伟大的想法吗?需要一些数据中的东西,但是这个也有一个“成功”的布尔值store.steampowered.com/api/appdetails?appids=218620
    【解决方案2】:

    看来问题出在这里:

    interface GameApiService{
    @GET("api.php?request=top100forever")
    fun getTop100(): Deferred<List<Game>>
    }
    

    你在你的代码中说你会得到一个 ListGame 对象,但你的 api 没有给你那个。

    这是来自您的 api 的响应:

    {
      "10": {
        "appid": 10,
        ...
        },
      "30": {
        "appid": 30,
        ...
        }
    }
    

    在此示例中,您的 API 返回的是一个对象 10,其中包含许多其他数据,以及一个对象 30,其中包含许多其他数据。 似乎 1030 包含相同的字段,因此可以帮助您。

    这意味着您可以创建一个新的数据类,可以称为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 类您正在使用 moshi 进行反序列化(如果您的适配器正在执行此部分,则可能不需要):

    interface GameApiService{
    
    @MoshiDeserialization
    @GET("api.php?request=top100forever")
    fun getTop100(): Deferred<GameServiceResponse>
    }
    

    我希望这是有道理的。

    【讨论】:

    • 谢谢,这已经是一个帮助:D 不确定是否可以进行 100 场比赛。有没有办法在不手动定义它们的情况下完成这项工作?
    • 您可以将其定义为泛型类型,并让GameServiceResponse 改为使用泛型类型
    猜你喜欢
    • 2018-11-27
    • 2017-03-09
    • 2020-05-14
    • 2019-11-20
    • 1970-01-01
    • 2020-03-07
    • 1970-01-01
    • 2019-03-04
    • 2018-03-02
    相关资源
    最近更新 更多