如何在Kotlin中解析带索引键的JSON字典?
Kotlin解析带数字索引键的JSON伪数组问题
问题背景
作为Kotlin新手,我在解析JSON时遇到了一个小难题:需要把JSON里带有"0"、"1"这类数字索引键的picks部分解析成ArrayList<Picks>,但不知道该怎么循环处理这种结构。先贴出我的JSON示例和最初的解析代码:
示例JSON
{ "orders": { "id": 1, "base_order": { "id": 1, "price": 10 }, "picks": { "0": { "name": "Omelette du fromage" }, "1": { "name": "Baguette" } } } }
最初的解析代码
Orders类
@Parcel class Orders { var id = 0L var baseOrder = BaseOrder() var picks = ArrayList<Picks>() fun loadFromJson(json: JsonObject) { id = readLong(json, TAG_ID) json.get(TAG_BASE_ORDER)?.let { if (it.isJsonObject) baseOrder.loadFromJson(it.asJsonObject) } } companion object { private const val TAG_ID = "id" private const val TAG_BASE_ORDER = "base_order" } }
BaseOrder类
@Parcel class BaseOrder { var id = 0L var price = 0f fun loadFromJson(json: JsonObject) { id = readLong(json, TAG_ID) price = readFloat(json, TAG_PRICE) } companion object { private const val TAG_ID = "id" private const val TAG_PRICE = "price" } }
Picks类
@Parcel class Picks { var name = "" fun loadFromJson(json: JsonObject) { name = readString(json, TAG_NAME) } companion object { private const val TAG_NAME = "name" } }
顺便提一句,这种用数字索引作为键、模拟数组结构的JSON叫做伪数组(或对象模拟数组),它本质是JSON对象而非标准JSON数组(标准数组用[]包裹),搜这个关键词能找到更多相关解法。
我找到的解决方法
经过摸索,我终于写出了可行的解析代码,核心是遍历JSON对象的键值对来处理picks:
@Parcel class Orders { var id = 0L var baseOrder = BaseOrder() var picks = ArrayList<Picks>() var pickItem = Picks() // 临时缓冲对象 fun loadFromJson(json: JsonObject) { id = readLong(json, TAG_ID) json.get(TAG_BASE_ORDER)?.let { if (it.isJsonObject) baseOrder.loadFromJson(it.asJsonObject) } // 解析picks部分 json.get("picks")?.let { if (it.isJsonObject) it.asJsonObject.entrySet().forEach() { it.value.let { if (it.isJsonObject) { pickItem.loadFromJson(it.asJsonObject)// 将JSON项解析到缓冲对象 picks.add(pickItem) // 添加到数组 pickItem = Picks() // 重置缓冲对象 } } } } } companion object { private const val TAG_ID = "id" private const val TAG_BASE_ORDER = "base_order" } }
这段代码能正常工作,但用了临时缓冲对象,感觉可以优化。
优化建议
作为Kotlin新手,其实可以去掉临时缓冲对象,直接在循环里创建新的Picks实例,代码会更简洁清晰:
优化后的解析逻辑
@Parcel class Orders { var id = 0L var baseOrder = BaseOrder() var picks = ArrayList<Picks>() fun loadFromJson(json: JsonObject) { id = readLong(json, TAG_ID) // 解析base_order json.get(TAG_BASE_ORDER)?.takeIf { it.isJsonObject }?.let { baseOrder.loadFromJson(it.asJsonObject) } // 解析picks json.get(TAG_PICKS)?.takeIf { it.isJsonObject }?.let { picksObj -> picksObj.asJsonObject.entrySet().forEach { (_, value) -> value.takeIf { it.isJsonObject }?.let { pickJson -> val pick = Picks().apply { loadFromJson(pickJson.asJsonObject) } picks.add(pick) } } } } companion object { private const val TAG_ID = "id" private const val TAG_BASE_ORDER = "base_order" private const val TAG_PICKS = "picks" // 把picks的key也定义为常量,避免硬编码 } }
优化点说明
- 去掉临时缓冲对象:直接在循环中创建
Picks实例,用apply函数一次性完成初始化和解析,代码更流畅,也避免了重复重置缓冲的步骤。 - 用
takeIf替代嵌套判断:让条件判断更简洁,可读性更强。 - 添加
TAG_PICKS常量:把硬编码的字符串提取为常量,符合代码规范,也方便后续维护。
如果之后想进一步简化解析流程,还可以尝试用Gson、Moshi这类成熟的JSON解析库,它们支持自定义适配器来自动处理这种伪数组结构,不用手动写遍历逻辑~
内容的提问来源于stack exchange,提问作者Mathieu
相关产品推荐
相关产品推荐

