Android开发:如何将动态结构JSON转为Kotlin Data Class?
处理动态键的JSON转Kotlin Data Class方案
问题背景
作为Android开发新手,我正在用Retrofit+Coroutines做开发,需要把秘鲁、德国的国家信息JSON转换成Kotlin Data Class,但遇到了属性键随国家变化的问题:
languages、currencies、nativeName的键是动态的(比如秘鲁货币键是PEN,德国是EUR;秘鲁有3种语言,德国仅1种)- 部分字段仅部分国家存在(比如德国有
flag和maps字段,秘鲁没有)
秘鲁JSON示例
{ "name": { "common": "Peru", "official": "Republic of Peru", "nativeName": { "aym": { "official": "Piruw Suyu", "common": "Piruw" }, "que": { "official": "Piruw Ripuwlika", "common": "Piruw" }, "spa": { "official": "República del Perú", "common": "Perú" } } }, "tld": [".pe"], "cca2": "PE", "ccn3": "604", "cca3": "PER", "cioc": "PER", "independent": true, "status": "officially-assigned", "unMember": true, "currencies": { "PEN": { "name": "Peruvian sol", "symbol": "S/ " } }, "idd": { "root": "+5", "suffixes": ["1"] }, "capital": ["Lima"], "altSpellings": ["PE", "Republic of Peru", "República del Perú"], "region": "Americas", "subregion": "South America", "languages": { "aym": "Aymara", "que": "Quechua", "spa": "Spanish" }, "latlng": [-10.0, -76.0], "landlocked": false, "borders": ["BOL", "BRA", "CHL", "COL", "ECU"], "area": 1285216.0, "demonyms": { "eng": {"f": "Peruvian", "m": "Peruvian"}, "fra": {"f": "Péruvienne", "m": "Péruvien"} }, "population": 32971846, "timezones": ["UTC-05:00"], "continents": ["South America"], "flags": { "png": "https://flagcdn.com/w320/pe.png", "svg": "https://flagcdn.com/pe.svg" }, "capitalInfo": {"latlng": [-12.05, -77.05]} }
德国JSON示例
{ "name": { "common": "Germany", "official": "Federal Republic of Germany", "nativeName": { "deu": { "official": "Bundesrepublik Deutschland", "common": "Deutschland" } } }, "tld": [".de"], "cca2": "DE", "ccn3": "276", "cca3": "DEU", "cioc": "GER", "independent": true, "status": "officially-assigned", "unMember": true, "currencies": { "EUR": {"name": "Euro", "symbol": "€"} }, "idd": {"root": "+4", "suffixes": ["9"]}, "capital": ["Berlin"], "altSpellings": ["DE", "Federal Republic of Germany", "Bundesrepublik Deutschland"], "region": "Europe", "subregion": "Western Europe", "languages": {"deu": "German"}, "latlng": [51.0, 9.0], "landlocked": false, "borders": ["AUT", "BEL", "CZE", "DNK", "FRA", "LUX", "NLD", "POL", "CHE"], "area": 357114.0, "demonyms": { "eng": {"f": "German", "m": "German"}, "fra": {"f": "Allemande", "m": "Allemand"} }, "flag": "🇩🇪", "maps": { "googleMaps": "https://goo.gl/maps/mD9FBMq1nvXUBrkv6", "openStreetMaps": "https://www.openstreetmap.org/relation/51477" }, "population": 83240525, "timezones": ["UTC+01:00"], "continents": ["Europe"], "flags": { "png": "https://flagcdn.com/w320/de.png", "svg": "https://flagcdn.com/de.svg" }, "capitalInfo": {"latlng": [52.52, 13.4]} }
解决方法
1. 用Map处理动态键字段
对于键不固定的字段,直接用Map<String, T>定义,适配任意数量的动态键值对:
// 嵌套数据类定义 data class Name( val common: String, val official: String, // nativeName的键为语言代码,值为对应名称对象 val nativeName: Map<String, NativeName> ) data class NativeName( val official: String, val common: String ) data class Currency( val name: String, val symbol: String? // 部分货币无symbol,设为可空 ) data class Demonym( val f: String, val m: String ) data class Flags( val png: String, val svg: String ) data class CapitalInfo( val latlng: List<Double> ) data class Maps( val googleMaps: String, val openStreetMaps: String ) data class Idd( val root: String, val suffixes: List<String> )
2. 定义顶层Country数据类
包含所有可能的字段,仅部分国家存在的字段标记为可空:
data class Country( val name: Name, val tld: List<String>, val cca2: String, val ccn3: String, val cca3: String, val cioc: String, val independent: Boolean, val status: String, val unMember: Boolean, // currencies的键为货币代码,值为Currency对象 val currencies: Map<String, Currency>, val idd: Idd, val capital: List<String>, val altSpellings: List<String>, val region: String, val subregion: String, // languages的键为语言代码,值为语言名称 val languages: Map<String, String>, val latlng: List<Double>, val landlocked: Boolean, val borders: List<String>?, // 内陆国可能无边界,设为可空 val area: Double, val demonyms: Map<String, Demonym>, val population: Long, val timezones: List<String>, val continents: List<String>, val flags: Flags, val capitalInfo: CapitalInfo, // 可选字段,部分国家才有 val flag: String?, val maps: Maps? )
3. Retrofit序列化适配
无论用Gson还是Moshi,都能自动解析Map类型:
- Gson:Retrofit默认集成,直接使用
GsonConverterFactory.create()即可 - Moshi:添加Kotlin支持依赖后,无需额外配置就能解析Map结构
4. 访问动态键数据
解析后通过Map的方法获取目标数据:
// 获取秘鲁货币名称 val peruCurrencyName = country.currencies["PEN"]?.name // 获取德国语言名称 val germanLanguage = country.languages["deu"] // 遍历所有语言 country.languages.forEach { (code, name) -> println("语言代码:$code,名称:$name") }
5. 处理可选字段
对仅部分国家存在的字段,访问时需判空:
country.flag?.let { emojiFlag -> println("国旗emoji:$emojiFlag") } ?: run { println("使用PNG国旗链接:${country.flags.png}") }
内容的提问来源于stack exchange,提问作者OFFICIAL_SEB15
相关产品推荐
相关产品推荐

