2

I am having difficulty decoding this JSON with Kotlin Serialization. It works well when the data field is not empty. However when the data field is null and the errors field is present I get this runtime exception:

kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 7: Expected start of the object '{', but had ':' instead
    JSON input: {"data":null,"errors":[{"path":null,"locations":[{"line":3,"column":5,"sourceName":null}],"message":"Validation error of type FieldUndefined: Field 'mee' in type 'Query' is undefined @ 'mee'"}]})

The JSON pretty printed:

{
    "data": null,
    "errors": [{
        "path": null,
        "locations": [{
            "line": 3,
            "column": 5,
            "sourceName": null
        }],
        "message": "Validation error of type FieldUndefined: Field 'mee' in type 'Query' is undefined @ 'mee'"
    }]
}

The code which is mostly stolen from How to serialize a generic class with kontlinx.serialization? :

class ApiResponse<T>(
    @SerialName("data")
    val data: T? = null,
    @SerialName("errors")
    val errors: List<ErrorResponse>? = null
)

@Serializable
class ErrorResponse(
    val path: String? = null,
    val locations: List<Location>? = null,
    val errorType: String? = null,
    val message: String? = null
)

@Serializable
    data class Location(
    val line: Int? = 0,
    val column: Int? = 0,
    val sourceName: String? = null
)

@ExperimentalSerializationApi
class ApiResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<ApiResponse<T>> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ApiResponseDataSerializer") {
        val dataDescriptor = dataSerializer.descriptor
        element("data", dataDescriptor)
        element("errors", ErrorResponse.serializer().descriptor)
    }
    override fun deserialize(decoder: Decoder): ApiResponse<T> =
        decoder.decodeStructure(descriptor) {
            var data: T? = null
            var errors: List<ErrorResponse>? = null
            val listSerializer = ListSerializer(ErrorResponse.serializer())

            loop@ while (true) {
                when (val i = decodeElementIndex(descriptor)) {
                    0 -> data = decodeSerializableElement(descriptor, i, dataSerializer, null)
                    1 -> errors = decodeSerializableElement(descriptor, i, ListSerializer(ErrorResponse.serializer()), null)
                    CompositeDecoder.DECODE_DONE -> break
                    else -> throw SerializationException("Unknown index $i")
                }
            }
            ApiResponse(data, errors)
        }
    override fun serialize(encoder: Encoder, value: ApiResponse<T>) {
        encoder.encodeStructure(descriptor) {

            val listSerializer = ListSerializer(ErrorResponse.serializer())

            encodeNullableSerializableElement(descriptor, 0, dataSerializer, value.data)
            value.errors?.let {
                encodeNullableSerializableElement(descriptor, 1, listSerializer, it)
            }
        }
    }
}

I tried using decodeNullableSerializableElement, but I got a compilation error. I couldn't find a way to fix that.

Type mismatch: inferred type is KSerializer<T> but DeserializationStrategy<TypeVariable(T)?> was expected

Any help would be appreciated, I am very new to Android and Kotlin.

Brett
  • 1,647
  • 16
  • 34

1 Answers1

1

Always pays to come back after a good nights sleep. Not sure why I had so much trouble with decodeNullableSerializableElement yesterday, but today I played around and got it working.

Made 3 changes

  • Made T optional in class parameter
  • Added .nullable (could not get this to work yesterday) to both serialisers
  • Changed decodeSerializableElement to decodeNullableSerializableElement

Relevant changes below:

@ExperimentalSerializationApi
class ApiResponseSerializer<T>(private val dataSerializer: KSerializer<T?>) : KSerializer<ApiResponse<T>> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ApiResponseDataSerializer") {
        val dataDescriptor = dataSerializer.descriptor
        element("data", dataDescriptor.nullable)
        element("errors", ErrorResponse.serializer().descriptor.nullable)
    }
    override fun deserialize(decoder: Decoder): ApiResponse<T> =
        decoder.decodeStructure(descriptor) {
            var data: T? = null
            var errors: List<ErrorResponse>? = null
            val listSerializer = ListSerializer(ErrorResponse.serializer()).nullable

            loop@ while (true) {
                when (val i = decodeElementIndex(descriptor)) {
                    0 -> data = decodeNullableSerializableElement(descriptor, i, dataSerializer, null)
                    1 -> errors = decodeNullableSerializableElement(descriptor, i, listSerializer, null)
                    CompositeDecoder.DECODE_DONE -> break
                    else -> throw SerializationException("Unknown index $i")
                }
            }
            ApiResponse(data, errors)
        }
    override fun serialize(encoder: Encoder, value: ApiResponse<T>) {
        encoder.encodeStructure(descriptor) {

            val listSerializer = ListSerializer(ErrorResponse.serializer())

            encodeNullableSerializableElement(descriptor, 0, dataSerializer, value.data)
            value.errors?.let {
                encodeNullableSerializableElement(descriptor, 1, listSerializer, it)
            }
        }
    }
}
Brett
  • 1,647
  • 16
  • 34