skip to Main Content

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

2

Answers


  1. Chosen as BEST ANSWER

    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()
    

  2. If the JSON field is either JSONArray or JSONObject, You can use JsonElement.

    data class BaseResponse<T>(
       val data: T,
       val errors: JsonElement
    )
    
    interface ApiInterface {
       @GET("test")
       suspend fun test(): BaseResponse<String>
    }
    

    And you can get errors like this.

    val response = api.test()
    val data: String = response.data
    val errors: Map<String, String?> = response.errors.parseErrors()
    

    parseErrors Kotlin extension

    fun JsonElement.parseErrors(): Map<String, String?> {
        return if (this.isJsonObject) {
            this.asJsonObject.asMap().map { (key, value) ->
                key!! to when {
                    value.isJsonPrimitive && value.asJsonPrimitive.isString -> value.asString
                    else -> null
                }
            }.toMap()
        } else emptyMap()
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search