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

Android中Retrofit如何处理动态Key的JSON响应?

在Retrofit中适配CoinGecko动态Key的JSON响应

我在Android应用里用Retrofit和CoinGecko API交互,目标调用的端点是:
https://api.coingecko.com/api/v3/simple/price?ids=01coin&vs_currencies=btc

它返回的JSON结构如下:

{
  "01coin": {
    "btc": 1.3651e-8
  }
}

这里的01coin和btc都是由请求参数动态生成的,换查询币种后响应结构会跟着变。我之前调用其他接口的代码是这样的:

Retrofit接口代码

@GET("coins/list")
suspend fun getCoinsList(): Response<ResponseCoinsList>

对应响应类代码

class ResponseCoinsList : ArrayList<ResponseCoinsList.ResponseCoinsListItem>() {
    data class ResponseCoinsListItem(
        @SerializedName("id")
        val id: String?, // 01coin
        @SerializedName("name")
        val name: String, // 01coin
        @SerializedName("symbol")
        val symbol: String? // zoc
    )
}

现在需要解决的问题是:怎么定义适配这种动态JSON对象的响应类?


解决方案1:直接使用嵌套Map(最简洁)

因为响应的外层Key是动态的币种ID,内层Key是动态的计价货币,直接用Map<String, Map<String, Double>>作为响应类型即可,不需要自定义数据类,Gson会自动完成解析。

调整后的Retrofit接口

@GET("simple/price")
suspend fun getSimplePrice(
    @Query("ids") ids: String,
    @Query("vs_currencies") vsCurrencies: String
): Response<Map<String, Map<String, Double>>>

使用示例

val response = api.getSimplePrice("01coin", "btc")
val btcPrice = response.body()?.get("01coin")?.get("btc")

如果一次查询多个币种或计价货币,比如ids=01coin,bitcoin&vs_currencies=btc,usd,也能直接通过Map的Key获取对应数据。


解决方案2:自定义数据类结合Map(兼顾类型安全与灵活性)

如果常用固定的几种计价货币(比如btc、usd),可以定义一个数据类来封装内层价格,外层依然用Map来兼容动态的币种ID:

定义内层数据类

data class CurrencyPriceDetail(
    @SerializedName("btc") val btcPrice: Double?,
    @SerializedName("usd") val usdPrice: Double?
    // 可根据需求添加更多常用计价货币的字段
)

调整后的Retrofit接口

@GET("simple/price")
suspend fun getSimplePrice(
    @Query("ids") ids: String,
    @Query("vs_currencies") vsCurrencies: String
): Response<Map<String, CurrencyPriceDetail>>

使用示例

val response = api.getSimplePrice("01coin", "btc,usd")
val coinData = response.body()?.get("01coin")
val btcPrice = coinData?.btcPrice
val usdPrice = coinData?.usdPrice

这种方式既可以通过属性直接访问常用价格,遇到不常用的计价货币时,还可以通过Gson的toJsonTree等方法动态获取(或者直接把CurrencyPriceDetail改成Map<String, Double>,灵活度更高)。


解决方案3:自定义TypeAdapter(复杂场景专用)

如果需要把动态Key的结构转换成列表等自定义格式,可以编写Gson的TypeAdapter来实现解析逻辑。比如把响应转成List<CurrencyPrice>,其中每个元素包含币种ID和对应的价格Map:

定义目标数据类

data class CurrencyPrice(
    val currencyId: String,
    val priceMap: Map<String, Double>
)

编写TypeAdapter

class CurrencyPriceTypeAdapter : TypeAdapter<List<CurrencyPrice>>() {
    override fun write(out: JsonWriter, value: List<CurrencyPrice>?) {
        // 如果需要序列化可以实现此方法,这里只处理解析
    }

    override fun read(`in`: JsonReader): List<CurrencyPrice> {
        val list = mutableListOf<CurrencyPrice>()
        `in`.beginObject()
        while (`in`.hasNext()) {
            val currencyId = `in`.nextName()
            val priceMap = mutableMapOf<String, Double>()
            `in`.beginObject()
            while (`in`.hasNext()) {
                val currency = `in`.nextName()
                val price = `in`.nextDouble()
                priceMap[currency] = price
            }
            `in`.endObject()
            list.add(CurrencyPrice(currencyId, priceMap))
        }
        `in`.endObject()
        return list
    }
}

配置Retrofit使用自定义TypeAdapter

在构建Retrofit时,给Gson添加这个TypeAdapter:

val gson = GsonBuilder()
    .registerTypeAdapter(object : TypeToken<List<CurrencyPrice>>() {}.type, CurrencyPriceTypeAdapter())
    .create()

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.coingecko.com/api/v3/")
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()

调整后的Retrofit接口

@GET("simple/price")
suspend fun getSimplePrice(
    @Query("ids") ids: String,
    @Query("vs_currencies") vsCurrencies: String
): Response<List<CurrencyPrice>>

这种方式适合有特殊结构转换需求的场景,但步骤繁琐,一般前两种方案足够覆盖大部分使用场景。


内容的提问来源于stack exchange,提问作者Mohsen Mohebi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 11:17:45