使用Kotlin+Retrofit+Moshi解析JSON数组为TopCrypto对象的问题
问题描述
使用Kotlin、Retrofit和Moshi开发时,调用CoinGecko API返回如下JSON数组:
[ { "id": "bitcoin", "symbol": "btc", "name": "Bitcoin", "image": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579", "current_price": 21917, "price_change_24h": -785.2614865719079, // 其他字段省略 }, // 更多加密货币对象 ]
定义了两个数据类,希望将API返回的数组封装到TopCrypto对象中:
data class TopCrypto( val cryptos: List<CryptoStock>? = null ) data class CryptoStock( val id: String? = null, val symbol: String? = null, val image: String? = null, val currentPrice: String? = null, val priceChange: Double? = null )
Service接口定义如下:
interface CryptoService { @GET("coins/markets") fun getTopCrypto( @Query("vs_currency") vsCurrency: String, @Query("per_page") limit: Int, @Query("order") order: String = "market_cap_desc" ): Call<TopCrypto> }
Api配置和CryptoManager代码见原提问,目前运行时抛出错误:Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $,需要解决JSON数组到TopCrypto对象的映射问题。
解决方案
1. 修正数据类的字段映射
JSON中的current_price和price_change_24h字段,需要通过@Json注解和Kotlin数据类的属性名对应,否则Moshi无法正确解析,同时注意字段类型匹配:
import com.squareup.moshi.Json data class CryptoStock( val id: String? = null, val symbol: String? = null, val image: String? = null, @Json(name = "current_price") val currentPrice: Double? = null, @Json(name = "price_change_24h") val priceChange: Double? = null )
2. 自定义Moshi适配器解析数组为TopCrypto
因为API返回的是数组,但我们需要封装到TopCrypto对象中,编写自定义适配器将JSON数组直接赋值给TopCrypto的cryptos属性:
import com.squareup.moshi.FromJson import com.squareup.moshi.ToJson class TopCryptoAdapter { @FromJson fun fromJson(cryptoList: List<CryptoStock>): TopCrypto { return TopCrypto(cryptoList) } @ToJson fun toJson(topCrypto: TopCrypto): List<CryptoStock>? { return topCrypto.cryptos } }
3. 将适配器添加到Moshi实例中
修改Api配置中的Moshi构建代码,加入自定义适配器:
object Api { private val BASE_URL = "https://api.coingecko.com/api/v3/" private val moshi = Moshi.Builder() .add(TopCryptoAdapter()) // 添加自定义适配器 .add(KotlinJsonAdapterFactory()) .build() private val retrofit = Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create(moshi)) .baseUrl(BASE_URL) .build() val retrofitService: CryptoService by lazy { retrofit.create(CryptoService::class.java) } }
4. 调整CryptoManager的状态逻辑
修正原代码中cryptoResponse的Composable状态获取方式,避免不必要的remember调用:
class CryptoManager { private val _cryptoResponse = mutableStateOf(TopCrypto()) val cryptoResponse: State<TopCrypto> = _cryptoResponse init { getCryptos() } private fun getCryptos() { val service = Api.retrofitService.getTopCrypto("usd", 10) service.enqueue(object : Callback<TopCrypto> { override fun onResponse(call: Call<TopCrypto>, response: Response<TopCrypto>) { if (response.isSuccessful) { response.body()?.let { _cryptoResponse.value = it Log.d("crypto", "${_cryptoResponse.value}") } } else { Log.d("error", "${response.errorBody()?.string()}") } } override fun onFailure(call: Call<TopCrypto>, t: Throwable) { Log.d("error", "${t.message}") } }) } }
修改完成后,Moshi会自动将API返回的JSON数组转换为List<CryptoStock>,再通过自定义适配器封装到TopCrypto对象中,解决类型不匹配的错误。
内容的提问来源于stack exchange,提问作者Jedrelek
相关产品推荐
相关产品推荐

