如何在Android Kotlin中使用Moshi和Retrofit解析具有动态键的嵌套JSON?

7

我该如何在Android kotlin,Moshi和Retrofit中解析具有嵌套动态键的JSON?

我从alpha-vantage获取了这个JSON。

格式示例:

{
    "Meta Data": {
        "1. Information": "Intraday (15min) open, high, low, close prices and volume",
        "2. Symbol": "AAME",
        "3. Last Refreshed": "2019-11-18 16:00:00",
        "4. Interval": "15min",
        "5. Output Size": "Compact",
        "6. Time Zone": "US/Eastern"
     },
    "Time Series (15min)": {//Dynamic - > Time Series (5min) / Time Series (30min)
        "2019-11-18 16:00:00": {//Dynamic
            "1. open": "1.6700",
            "2. high": "1.6700",
            "3. low": "1.5700",
            "4. close": "1.5700",
            "5. volume": "1521"
        },
        "2019-11-18 15:45:00": {//Dynamic
            "1. open": "1.6600",
            "2. high": "1.7400",
            "3. low": "1.6600",
            "4. close": "1.7400",
            "5. volume": "355"
        }
    }
}

我试过使用自定义适配器,但是我无法找到一种方法来解析双重嵌套的动态键。

目前我使用手动解析:

fun convertJsonToItemDetails(jso: JSONObject) {
    val meta: JSONObject? = jso.optJSONObject("Meta Data")
    var metaData: ItemMetaData? = null
    meta?.apply {
        val information = optString("1. Information")
        val symbol = optString("2. Symbol")
        val lastRefreshed = optString("3. Last Refreshed")
        val interval = optString("4. Interval")
        val outputSize = optString("5. Output Size")
        val timeZone = optString("6. Time Zone")
        metaData =
            ItemMetaData(information, symbol, lastRefreshed, interval, outputSize, timeZone)
    }
    if (metaData == null) {
        //TODO return error object
        return
    }

    val timeSeriesJSON = jso.optJSONObject("Time Series (${metaData?.interval})")
    val timeSeires = HashMap<String, IntervalOutput>()
    if (timeSeriesJSON == null) {
        //TODO return error object
        return
    }
    for (key in timeSeriesJSON.keys()) {
        val intervalOutPutJSON = timeSeriesJSON.getJSONObject(key)
        val open = intervalOutPutJSON.getString("1. open")
        val high = intervalOutPutJSON.getString("2. high")
        val low = intervalOutPutJSON.getString("3. low")
        val close = intervalOutPutJSON.getString("4. close")
        val volume = intervalOutPutJSON.getString("5. volume")
        timeSeires[key] = IntervalOutput(open, high, low, close, volume)
    }
    val itemDetails = ItemDetails(metaData, timeSeires)
    _itemDetails.value = itemDetails
}

由于JSON是当今数据交换的新标准,难道没有可用的JSON解析库吗? - Mikkel
是的,你说得对,JSON 是新标准,但我没有找到一种使用 Moshi 解析这种用例的方法。@Mikkel - Maor Hadad
在'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.2'中有类似于@JsonRawValue的东西。但它不是Moshi,因此只是一条注释;-) - longi
同意@longi的评论,使用com.fasterxml.jackson.module:jackson非常容易,甚至不需要编写自定义适配器。 - Shripad Jadhav
3
我已经花了一些时间尝试为您想出一个解决方案,但是您的JSON键名包含空格,这最终导致了Moshi的Kotlin Codegen出现故障。我已经为此提交了一个问题(https://github.com/square/moshi/issues/1052),并决定暂时放弃解决这个问题,因为我没有时间为您编写一个适配器工厂。 - th3ant
显示剩余2条评论
2个回答

2
使用JSONObject keys()获取动态键,然后迭代每个特定的键以获取动态值。
打印所有第一层的JSONObject非常简短,如下所示。
var dynamicJSON: String = {
    "Meta Data": {
        "1. Information": "Intraday (15min) open, high, low, close prices and volume",
        "2. Symbol": "AAME",
        "3. Last Refreshed": "2019-11-18 16:00:00",
        "4. Interval": "15min",
        "5. Output Size": "Compact",
        "6. Time Zone": "US/Eastern"
     },
    "Time Series (15min)": {//Dynamic - > Time Series (5min) / Time Series (30min)
        "2019-11-18 16:00:00": {//Dynamic
            "1. open": "1.6700",
            "2. high": "1.6700",
            "3. low": "1.5700",
            "4. close": "1.5700",
            "5. volume": "1521"
        },
        "2019-11-18 15:45:00": {//Dynamic
            "1. open": "1.6600",
            "2. high": "1.7400",
            "3. low": "1.6600",
            "4. close": "1.7400",
            "5. volume": "355"
        }
    }
}  

然后,您需要将其解析为JSONObject,并对JSONObject的"Meta Data""Time Series (15min)"进行相同的处理过程。

val dynamicjson: JSONObject = JSONObject(dynamicJSON)
val keys: Iterator<*> = dynamicjson.keys()

while (keys.hasNext()) { // loop to get the dynamic key
   val currentDynamicKey = keys.next() as String
   // get the value of the dynamic key
   val currentDynamicValue: JSONObject = dynamicjson.getJSONObject(currentDynamicKey)
   // do something here with the value... or either make another while loop to Iterate further

   Log.e("JSON Value", currentDynamicValue.toString())
}

当我执行代码时,输出如下:

2019-12-12 15:21:08.399 19798-19798/com.animusabhi.dynamicjsonparsing E/JSON Value: {"1. 信息":"日内(15分钟)开盘价、最高价、最低价、收盘价和成交量","2. 符号":"AAME","3. 最近刷新时间":"2019-11-18 16:00:00","4. 间隔":"15分钟","5. 输出大小":"紧凑型","6. 时区":"美国/东部"}

2019-12-12 15:21:09.158 19798-19798/com.animusabhi.dynamicjsonparsing E/JSON Value{"2019-11-18 16:00:00":{"1. 开盘价":"1.6700","2. 最高价":"1.6700","3. 最低价":"1.5700","4. 收盘价":"1.5700","5. 成交量":"1521"},"2019-11-18 15:45:00":{"1. 开盘价":"1.6600","2. 最高价":"1.7400","3. 最低价":"1.6600","4. 收盘价":"1.7400","5. 成交量":"355"}}

请检查并让我知道是否不起作用。

0
只需将您的 JSON 字符串传递给此方法,它肯定会起作用。
private void parseJson(String jsonString) {
    try {
        //  pass your response json string here
        JSONObject object = new JSONObject(jsonString);

        Iterator<String> it = object.keys();
        while (it.hasNext()) {
            String key = it.next();
            try {
                if (object.get(key) instanceof JSONObject) {
                    Log.e("Main OBJ", key);
                    JSONObject object2 = object.getJSONObject(key);
                    Iterator<String> it2 = object2.keys();
                    while (it2.hasNext()) {
                        String key2 = it2.next();
                        if (object2.get(key2) instanceof JSONObject) {
                            Log.e("Sub OBJ", key2);
                            JSONObject object3 = object2.getJSONObject(key2);
                            Iterator<String> it3 = object3.keys();
                            while (it3.hasNext()) {
                                String key4 = it3.next();
                                Log.e("Values", key4);
                            }
                        } else {
                            Log.e("Values", object2.getString(key2));
                        }
                    }

                } else {
                    System.out.println(key + ":" + object.getString(key));
                }
            } catch (Throwable e) {
                try {
                    System.out.println(key + ":" + object.getString(key));
                } catch (Exception ee) {
                    ee.printStackTrace();
                }
                e.printStackTrace();
            }
        }

    } catch (JSONException e) {
        e.printStackTrace();
    }
}

输出:

Main OBJ: Meta Data
Values: Intraday (15min) open, high, low, close prices and volume
Values: AAME
Values: 2019-11-18 16:00:00
Values: 15min
Values: Compact
Values: US/Eastern
Main OBJ: Time Series (15min)
Sub OBJ: 2019-11-18 16:00:00
Values: 1. open
Values: 2. high
Values: 3. low
Values: 4. close
Values: 5. volume
Sub OBJ: 2019-11-18 15:45:00
Values: 1. open
Values: 2. high
Values: 3. low
Values: 4. close
Values: 5. volume

它没有回答我的问题。它被默认选择,因为赏金期已结束。 - Maor Hadad
这不是正确的答案吗?我认为你不能使用JSON解析库,因为你需要先指定所有键,如果任何键更改,它们会直接抛出错误响应,并且你的响应键是动态的。 - Gundu Bandgar
2
这不是正确的答案。你可以使用Map解析动态键。问题在于嵌套的动态键。另外,就像其他人在这里说的那样,使用其他库也是可能的(我没有检查过)。 - Maor Hadad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接