使用Kotlinx.serialization将JSON数组解析为Map<String, String>

9

我正在编写一个Kotlin多平台项目(JVM/JS),并尝试使用Kotlinx.serialization将HTTP Json数组响应解析为Map。

JSON格式类似于:

[{"someKey": "someValue"}, {"otherKey": "otherValue"}, {"anotherKey": "randomText"}]

目前,我能够得到JSON字符串,但是我找不到任何文档来帮助我构建Map或其他类型的对象。文档中都是关于如何序列化静态对象的。

我不能使用@SerialName,因为键值不是固定的。

当我尝试返回Map<String, String>时,会出现以下错误:

Can't locate argument-less serializer for class kotlin.collections.Map. For generic classes, such as lists, please provide serializer explicitly.

在最后,我希望得到一个 Map<String, String> 或者一个 List<MyObject>,其中我的对象可以是 MyObject(val id: String, val value: String)
有没有办法做到这一点? 否则,我想只需编写一个字符串读取器即可解析我的数据。

在 JSON 中是否可能出现重复的键? - Mosius
1
如果有机会重构你的JSON,请尝试将所有对象放在一个对象中,例如{"someKey": "someValue", "otherKey": "otherValue"},这是更好的数据结构。 - Mosius
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/custom_serializers.md - royB
为什么不将其解析为 List<Map<String,String>>,作为客户端的内部步骤,然后再将其转换为所需的类型之一呢? - Laurence
@Laurence,那太好了,但我不知道如何将其解析为List。我得到了相同的“找不到无参数序列化程序”的错误,但现在是针对List。 - Rulo Mejía
显示剩余3条评论
2个回答

10

您可以像这样实现自己的简单DeserializationStrategy

object JsonArrayToStringMapDeserializer : DeserializationStrategy<Map<String, String>> {

    override val descriptor = SerialClassDescImpl("JsonMap")

    override fun deserialize(decoder: Decoder): Map<String, String> {

        val input = decoder as? JsonInput ?: throw SerializationException("Expected Json Input")
        val array = input.decodeJson() as? JsonArray ?: throw SerializationException("Expected JsonArray")

        return array.map {
            it as JsonObject
            val firstKey = it.keys.first()
            firstKey to it[firstKey]!!.content
        }.toMap()


    }

    override fun patch(decoder: Decoder, old: Map<String, String>): Map<String, String> =
        throw UpdateNotSupportedException("Update not supported")

}


fun main() {
    val map = Json.parse(JsonArrayToStringMapDeserializer, data)
    map.forEach { println("${it.key} - ${it.value}") }
}

完美运行!我正在使用一个expect类解析器进行解决方案,并使用klaxon在jvm中编写两个实现(我已经完成),并使用JSON.parse在js中编写,但这样可以涵盖两种情况。谢谢! - Rulo Mejía
兄弟,你是怎么做的 SerialClassDescImpl("JsonMap")? 我也需要定义它吗? - Richard Domingo

0

由于@alexander-egger的回答看起来有点过时,这里提供一个现代化的答案:

object ListAsMapDeserializer: KSerializer<Map<String, String>> {

    private val mapSerializer = ListSerializer(MapEntrySerializer(String.serializer(), String.serializer()))

    override val descriptor: SerialDescriptor = mapSerializer.descriptor

    override fun deserialize(decoder: Decoder): Map<String, String> {
        return mapSerializer.deserialize(decoder).associate { it.toPair() }
    }

    override fun serialize(encoder: Encoder, value: Map<String, String>) {
        mapSerializer.serialize(encoder, value.entries.toList())
    }
}

以及对其进行的测试:

@Test
fun listAsMap() {
    val jsonElement = json.parseToJsonElement("{ \"map\": [ {\"key1\":\"value1\"}, {\"key2\":\"value2\"} ] }")
    val testWithMap = json.decodeFromJsonElement<TestWithMap>(jsonElement)
    assertEquals(mapOf("key1" to "value1", "key2" to "value2"), testWithMap.map)
}

@Test
fun mapAsList() {
    val jsonElement = json.parseToJsonElement("{ \"map\": [ {\"key1\":\"value1\"}, {\"key2\":\"value2\"} ] }")
    val testWithMap = TestWithMap(mapOf("key1" to "value1", "key2" to "value2"))
    val serialized = json.encodeToJsonElement(TestWithMap.serializer(), testWithMap)
    assertEquals(jsonElement, serialized)
}

@Serializable
data class TestWithMap(
    @Serializable(with = ListAsMapDeserializer::class)
    val map: Map<String, String>
)

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