You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 23:48:14