Android使用Retrofit解析动态键名JSON响应的实现方案
Retrofit适配动态联赛键JSON结构的解析方案
问题核心
你遇到的解析失败问题本质是两个原因:
keySeriesNews字段是动态键值对结构:键为任意联赛名称(如Indian Premier League),值为对应联赛的新闻数组,硬编码固定字段名的模型无法适配动态变化的键- 直接用
org.json.JSONObject作为Retrofit模型字段类型时,Gson等序列化器默认没有适配该类型的转换逻辑,所以会拿到空对象
推荐方案:Map类型直接建模(无额外解析代码)
这是Retrofit+Gson/Moshi解析动态键JSON的标准写法,你已经写好的League、Image、Competition实体类可以完全复用,只需要修改顶层和keySeriesNews对应的模型即可:
// 顶层响应模型 data class HomeNewsParentModel( @SerializedName("keySeriesNews") var keySeriesNews: Map<String, List<League>> = emptyMap() )
使用方式
接口请求成功后,直接遍历Map即可同时拿到联赛名和对应新闻列表,支持任意数量、任意名称的联赛条目:
apiResponse.body()?.keySeriesNews?.forEach { (leagueName, newsList) -> // leagueName 就是动态返回的联赛名称,例如"Indian Premier League" // newsList 就是该联赛对应的新闻实体列表,和你之前硬编码解析出来的结构完全一致 println("当前联赛:$leagueName,新闻条数:${newsList.size}") }
备选方案:原始JSON手动解析
如果你需要直接处理原始JSON字符串,不要在Retrofit模型层传JSONObject,正确步骤如下:
- 把Retrofit接口的对应返回值类型设为
ResponseBody,拿到原始响应流
interface NewsApi { @GET("your/endpoint/path") suspend fun fetchHomeNews(): Response<ResponseBody> }
- 从原始响应字符串逐层解析
private fun parseJSONData(responseBody: ResponseBody){ try { val rootJson = JSONObject(responseBody.string()) // 先取出keySeriesNews对象 val seriesNewsObj = rootJson.getJSONObject("keySeriesNews") // 遍历所有动态联赛键 for (leagueName in seriesNewsObj.keys()) { val newsArray = seriesNewsObj.getJSONArray(leagueName) // 按需处理新闻数组,可手动逐个转换为League实体 Toast.makeText( requireContext(), "联赛:$leagueName,新闻数量:${newsArray.length()}", Toast.LENGTH_SHORT ).show() } } catch (e: JSONException) { e.printStackTrace() } }
你之前写的手动解析逻辑拿不到数据,是因为直接遍历了顶层JSON的key,顶层JSON仅存在
keySeriesNews一个键,根本没有遍历到内部的联赛名;同时如果传入的data参数是Retrofit解析失败后返回的空对象,二次转JSONObject自然会得到空结果。
避坑提示
- 非必要不要在Gson序列化的模型中直接使用
org.json.JSONObject作为字段类型,除非你手动注册了对应类型的反序列化适配器,否则默认解析结果为空 - 所有键名动态、结构固定的JSON对象,都可以用
Map<键类型, 值类型>的方式建模,这是序列化器原生支持的写法,兼容性最好 - 该Map建模方式同时适用于Gson、Moshi、kotlinx.serialization等主流Android序列化库
内容的提问来源于stack exchange,提问作者Aitsam Ali
相关产品推荐
相关产品推荐

