Anyone aware of way to create custom deserializati...
# serialization
j
Anyone aware of way to create custom deserialization of some "map type" json in to a list (more in thread)....
json has field like following
Copy code
"route": {
"1644660208": "635561",
"1644660593": "523441",
"1644660829": "523461",
"1644660900": "523471",
"1644660953": "523481",
"1644660985": "523491"
...
}
the order here is important and need to convert in to a list (ideally of the 2nd values there)
d
Yes, most straight forward way to deserialise into
JsonObject
in your custom serializer, then do the conversion in there.
j
ok, thanks....will take a look and see if I can do that
e
if the keys are unique then the most straightforward thing will work just fine,
Copy code
@Serializable
data class MyData(
    val route: Map<String, String>
)

val myData = Json.decodeFromString(
    MyData.serializer(),
    """
        {
            "route": {
                "1644660208": "635561",
                "1644660593": "523441",
                "1644660829": "523461",
                "1644660900": "523471",
                "1644660953": "523481",
                "1644660985": "523491"
            }
        }
    """.trimIndent()
)
println(myData) // => MyData(route={1644660208=635561, 1644660593=523441, 1644660829=523461, 1644660900=523471, 1644660953=523481, 1644660985=523491})
unless you go out of your way to use HashMap, the default maps and sets in Kotlin preserve order
j
ah, nice....thanks, makes sense
e
it takes some effort but it is even possible to handle json-like objects with duplicated keys,
Copy code
@Serializable
data class MyData(
    val route: Multimap<String, String>
)

@Serializable(with = Multimap.Serializer::class)
data class Multimap<K, V>(val pairs: List<Pair<K, V>>) {
    class Serializer<K, V>(val kSerializer: KSerializer<K>, val vSerializer: KSerializer<V>) : KSerializer<Multimap<K, V>> {
        override val descriptor: SerialDescriptor = MapSerializer(kSerializer, vSerializer).descriptor

        override fun serialize(encoder: Encoder, value: Multimap<K, V>) {
            encoder.encodeCollection(descriptor, value.pairs.size) {
                value.pairs.forEachIndexed { i, (k, v) ->
                    encodeSerializableElement(descriptor, 2 * i, kSerializer, k)
                    encodeSerializableElement(descriptor, 2 * i + 1, vSerializer, v)
                }
            }
        }

        override fun deserialize(decoder: Decoder): Multimap<K, V> {
            val pairs = decoder.decodeStructure(descriptor) {
                if (decodeSequentially()) {
                    val size = decodeCollectionSize(descriptor)
                    List(size / 2) {
                        val k = decodeSerializableElement(descriptor, 2 * it, kSerializer)
                        val v = decodeSerializableElement(descriptor, 2 * it + 1, vSerializer)
                        k to v
                    }
                } else {
                    buildList {
                        while (true) {
                            val i = decodeElementIndex(descriptor)
                            if (i == CompositeDecoder.DECODE_DONE) break
                            val k = decodeSerializableElement(descriptor, i, kSerializer)
                            val j = decodeElementIndex(descriptor)
                            if (j == CompositeDecoder.DECODE_DONE) throw SerializationException("missing value")
                            val v = decodeSerializableElement(descriptor, j, vSerializer)
                            add(k to v)
                        }
                    }
                }
            }
            return Multimap(pairs)
        }
    }
}

val myData = Json.decodeFromString(
    MyData.serializer(),
    """
        {
            "route": {
                "1644660208": "635561",
                "1644660593": "523441",
                "1644660829": "523461",
                "1644660900": "523471",
                "1644660953": "523481",
                "1644660985": "523491",
                "1644660208": "635561",
                "1644660593": "523441",
                "1644660829": "523461",
                "1644660900": "523471",
                "1644660953": "523481"
            }
        }
    """.trimIndent()
)
println(myData) // => MyData(route=Multimap(pairs=[(1644660208, 635561), (1644660593, 523441), (1644660829, 523461), (1644660900, 523471), (1644660953, 523481), (1644660985, 523491), (1644660208, 635561), (1644660593, 523441), (1644660829, 523461), (1644660900, 523471), (1644660953, 523481)]))
but hopefully that is not what you are dealing with (it's not well-specified by the JSON specification how implementations should handle duplicate keys, and results do vary)
d
Since it's json you can just skip the
decodeSequentially
. It'll never happen.
Or rather, it probably won't. 🙂
p
I strongly suggest to change the backend response here.
j
Unfortunately I don't have any control of that
p
An object is an unordered collection of zero or more name/value pairs, where a name is a string and a value is a string, number, boolean, null, object, or array.
It's not good to break the spec and rely on specific serialization behavior
If you really want to go that way I suggest going with a json transformation: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#json-transformations
👍 1
e
if they require keeping multiple duplicate keys, json transformation is not going to help, because the dups will be lost in the intermediary JsonObject
(but if they're all unique and you just want to reshape to a list, that's fine)
p
Now with a test 😛
Hm but in the end just relying on kotlins ordered map might be easier so ephemients first solution is better as it’s less complex
j
it does though allow I think a cleaner abstraction
I only need the 2nd value so that simplifies that code just a little bit as well
pulled it in here and works great, thanks
e
element as JsonObject
can be just written as element.jsonObject 🙂
today i learned 1
j
this is what I have here now based on @Paul Woitaschek’s code
Copy code
object MapToList : JsonTransformingSerializer<List<String>>(ListSerializer(String.serializer())) {
    override fun transformDeserialize(element: JsonElement): JsonElement {
        val arrayEntries = element.jsonObject.map { (_, value) -> value }
        return JsonArray(arrayEntries)
    }
}
d
Also, consider using the builders.
buildJsonArray { ... }
p
What you actually want a flat list of strings?
Out of curiosity: what are these values?
j
bus stop references.....for a particular bus
p
And what are the keys and what are the values?
j
the values are the bus stop references (that's all I need)....the keys I think are just auto generated....not sure what history/why they were added
so, yeah, probably not the best json to use here but it's what it is
p
Screams for an apiV2 if nobody knows what that data is
691 Views