使用自定义解码器在 Kotlin 中反序列化 JSON 可为空的字段时遇到问题

Having trouble with deserialising JSON nullable fields in Kotlin with custom decoder

我在使用 Kotlin 序列化解码此 JSON 时遇到困难。当数据字段不为空时效果很好。但是,当数据字段为 null 且存在错误字段时,我收到此运行时异常:

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'"}]})

JSON 印刷精美:

{
    "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'"
    }]
}

主要是从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)
            }
        }
    }
}

我尝试使用 decodeNullableSerializableElement,但出现编译错误。我找不到解决这个问题的方法。

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

任何帮助将不胜感激,我是 Android 和 Kotlin 的新手。

睡个好觉后回来总是值得的。不知道为什么我昨天在使用 decodeNullableSerializableElement 时遇到了这么多麻烦,但今天我玩了一下并让它工作了。

进行了 3 次更改

  • 在 class 参数中使 T 可选
  • 向两个序列化器添加了 .nullable(昨天无法正常工作)
  • 将 decodeSerializableElement 更改为 decodeNullableSerializableElement

相关变更如下:

@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)
            }
        }
    }
}