androidjsonkotlinjson-deserialization

Deserialize json field that may be either array or map


I'm getting json data from server API to Android app (Kotlin Serialization + Retrofit)

I need to parse a Json with field errors. The issue is the structure of this field.

If there are no errors I get empty array:

"errors":[]

But if there are some errors, I get this structure, like a Map with dynamic key:

"errors":{"kode":"The kode field do not exist."}

The question:

What type should I use for errors field in my Serializable Kotlin data class to be able parse it in both cases?

If I use Map<String, String>, it works only if there are some errors. For json with empty errors I get exception:

Unexpected JSON token at offset 86: Expected start of the object '{', but had '[' instead at path: $.errors

I tried to use KSerializer to return empty Map if cannot deserialize errors as a Map

class TestDeserializer: KSerializer<Map<String, String>> {
    private val delegateMapSerializer = MapSerializer(String.serializer(), String.serializer())

    override val descriptor: SerialDescriptor
        get() = SerialDescriptor("Errors", delegateMapSerializer.descriptor)

    override fun deserialize(decoder: Decoder): Map<String, String> {
        return try  {
            decoder.decodeSerializableValue(delegateMapSerializer)
        } catch (e: Exception) {
            emptyMap()
        }
    }

    override fun serialize(encoder: Encoder, value: Map<String, String>) {
        TODO("Not yet implemented")
    }
}

And use it in my Kotlin data class:

@Serializable(with = TestDeserializer::class)
    val errors: Map<String, String>

But probably did smth wrong, because still get the same JsonDecodingException


Solution

  • Found another way to create Deserializer that checks if json element is an array and replaces it with empty map.

    class ErrorsDeserializer: JsonTransformingSerializer<Map<String, String>>(
        MapSerializer(String.serializer(), String.serializer())
    ) {
        override fun transformDeserialize(element: JsonElement): JsonElement {
            if (element is JsonArray) return JsonObject(emptyMap())
            return element
        }
    }
    

    Use it for errors in data class

    @Serializable(with = ErrorsDeserializer::class)
    val errors: Map<String, String> = emptyMap()