如何使用Retrofit解析含动态Key的JSON?附API响应与数据类示例
Hey there! Let's break down how to handle this JSON response with Retrofit—your current data classes just need a small tweak to match the API's structure properly.
First, let's recap what we're working with:
Your API response:
{ "country1":{ "id":"0", "name":"Jack" }, "country2":{ "id":"1", "name":"Rick" } }Your existing data classes:
data class Countries(@Expose var country: Map<String, Country>) data class Country(var id: String, var name: String)
The core issue here is that your Countries class expects a top-level field named country, but the API returns dynamic top-level keys (country1, country2, etc.) that directly map to Country objects. Here are two straightforward solutions:
Solution 1: Skip the Wrapper Class, Use Map<String, Country> Directly
Since the entire JSON response is just a map of string keys to Country objects, you can simplify things by using Map<String, Country> as the return type in your Retrofit interface. No need for the Countries wrapper unless you specifically need it for your code structure.
Step 1: Update Your Retrofit Interface
interface CountryApi { @GET("your-api-endpoint-path") suspend fun getCountries(): Response<Map<String, Country>> }
Step 2: Parse the Response
When you call the API, you can directly access and iterate over the map to work with each Country object:
val response = countryApi.getCountries() if (response.isSuccessful) { val countryMap = response.body() countryMap?.forEach { (countryKey, country) -> println("Country Key: $countryKey, ID: ${country.id}, Name: ${country.name}") // Add your logic to process each Country here } }
Solution 2: Adjust the Countries Wrapper Class
If you want to keep using the Countries class to wrap the map, you need to fix how Gson (Retrofit's default serializer) maps the JSON to your class. Since there's no single top-level field named country, we can tell Gson to capture all top-level dynamic keys directly into the map.
Updated Countries Class
data class Countries( @Expose // Empty SerializedName tells Gson to map all top-level keys to this map @SerializedName("") var country: Map<String, Country> )
For clearer naming (since "country" as the map name is a bit ambiguous):
data class Countries( @Expose @SerializedName("") var countryMap: Map<String, Country> )
Corresponding Retrofit Interface
interface CountryApi { @GET("your-api-endpoint-path") suspend fun getCountries(): Response<Countries> }
Parse the Response
val response = countryApi.getCountries() if (response.isSuccessful) { val countriesWrapper = response.body() countriesWrapper?.countryMap?.forEach { (countryKey, country) -> println("Country Key: $countryKey, ID: ${country.id}, Name: ${country.name}") } }
Quick Notes to Keep in Mind
- Make sure you have the Gson Converter Factory set up in your Retrofit instance (it's included by default, but double-check if you have a custom setup):
val retrofit = Retrofit.Builder() .baseUrl("your-base-url-here") .addConverterFactory(GsonConverterFactory.create()) .build() - Your
Countryclass is already perfect—its field names (id,name) exactly match the JSON keys, so no extraSerializedNameannotations are needed here.
内容的提问来源于stack exchange,提问作者Mohammad Babaei TB

