Reputation: 168
I am writing a Kotlin multiplatform project (JVM/JS) and I am trying to parse a HTTP Json array response into a Map using Kotlinx.serialization
The JSON is something like this:
[{"someKey": "someValue"}, {"otherKey": "otherValue"}, {"anotherKey": "randomText"}]
So far, I am able to get that JSON as String, but I can't find any documentation to help me build a Map or another kind of object. All of it says how to serialize static objects.
I can't use @SerialName
because the key is not fixed.
When I try to return a Map<String, String>
, I get this error:
Can't locate argument-less serializer for class kotlin.collections.Map. For generic classes, such as lists, please provide serializer explicitly.
At the end, I would like to get either a Map<String, String>
or a List<MyObject>
where my object could be MyObject(val id: String, val value: String)
Is there a way to do that? Otherwise I am thinking in just writing a String reader to be able to parse my data.
Upvotes: 9
Views: 9306
Reputation: 540
As the answer by @alexander-egger looks a bit outdated, here is a modern one:
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())
}
}
and tests for it :
@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>
)
Upvotes: 0
Reputation: 5300
You can implement you own simple DeserializationStrategy
like this:
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}") }
}
Upvotes: 10