通用接口的 Kotlin 序列化

Muk*_*oki 3 kotlin

尝试 kotlin 1.4.32 / 序列化 1.1.0 (和 kotlin 1.5.0 / 序列化 1.2.0) 我找不到序列化以下类层次结构的方法

interface Range<T:Comparable<T>>

@Serializable @SerialName("range")
class SimpleRange<T:Comparable<T>>: Range<T>

@Serializable @SerialName("multirange")
class MultiRange<T:Comparable<T>>: Range<T>
Run Code Online (Sandbox Code Playgroud)

我可以使用 SerializersModule序列化 a SimpleRange<Double>(声明为 a ),包括Range<Double>

polymorphic(Range::class) {
    subclass(SimpleRange.serializer(Double.serializer()))
}
Run Code Online (Sandbox Code Playgroud)

但我找不到一种方法来配置模块,使其可以序列化/反序列化一个SingleRange<Double>或一个SingleRange<Int>或一个MultiRange<String>或我可以在 SerializersModule 中声明的任何组合。例如,如果我添加subclass(SimpleRange.serializer(Int.serializer())到前一个,我会得到一个SerializerAlreadyRegisteredException

Мих*_*аль 6

对于没有通用字段的虚拟类,这应该足够了:

val module = SerializersModule {
    polymorphic(Range::class) {
        subclass(SimpleRange.serializer(PolymorphicSerializer(Comparable::class)))
        subclass(MultiRange.serializer(PolymorphicSerializer(Comparable::class)))
    }
}
Run Code Online (Sandbox Code Playgroud)

但如果这些类中有类型的字段T : Comparable<T>,那就更棘手了。一般来说,您也需要为Comparable接口声明多态序列化器,但问题是您无法将“原始类型”(StringIntDouble等)注册为多态序列化的子类( kotlinx.serialization 的限制)。在这种情况下,这些类型至关重要。

作为解决方法,您可以执行以下操作:

  1. 使用 kotlinx.serialization 1.2.0,它允许为泛型类声明上下文序列化器,因此您的模块应该变成:
@ExperimentalSerializationApi
val module = SerializersModule {
    contextual(Comparable::class) { it[0] } //use serializer of its first type parameter
}
Run Code Online (Sandbox Code Playgroud)
  1. 将您的字段类型重新声明TComparable<T>(在本例中本质上是相同的,但否则您会收到神秘的错误消息java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0)并用@Contextual注释标记它们:
@Serializable
@SerialName("range")
class SimpleRange<T : Comparable<T>>(@Contextual val min: Comparable<T>, @Contextual val max: Comparable<T>) : Range<T>
Run Code Online (Sandbox Code Playgroud)
  1. 不幸的是,不可能直接在序列化器模块中将泛型类的多态序列化器和上下文序列化器组合起来:
//Will not work due to loss of information about type parameter
polymorphic(Range::class) {
    subclass(SimpleRange.serializer(ContextualSerializer(Comparable::class)))
    subclass(MultiRange.serializer(ContextualSerializer(Comparable::class)))
}
Run Code Online (Sandbox Code Playgroud)

因此,为了保留类型参数信息,应通过具有具体化类型参数的辅助函数手动选择子类序列化器:

@Suppress("UNCHECKED_CAST")
inline fun <reified T : Range<K>, reified K : Comparable<K>> rangeSerializer(value: T): KSerializer<T> = when (value) {
    is SimpleRange<*> -> serializer<SimpleRange<K>>()
    is MultiRange<*> -> serializer<MultiRange<K>>()
    else -> throw NotImplementedError() // still required even for sealed interfaces, KT-21908
} as KSerializer<T>
Run Code Online (Sandbox Code Playgroud)

用法:

@ExperimentalSerializationApi
fun main() {
    val kotlinx = Json {
        serializersModule = module
    }

    val range1: SimpleRange<Int> = SimpleRange(1, 2)
    println(kotlinx.encodeToString(rangeSerializer(range1), range1)) // {"min":1,"max":2}
    val range2: SimpleRange<Double> = SimpleRange(1.0, 2.0)
    println(kotlinx.encodeToString(rangeSerializer(range2), range2)) // {"min":1.0,"max":2.0}
    val range3: Range<Double> = range2
    println(kotlinx.encodeToString(rangeSerializer(range3), range3)) // {"min":1.0,"max":2.0}
}
Run Code Online (Sandbox Code Playgroud)

更新

(反序列化)

由于我们在序列化此数据时放弃了多态序列化,因此生成的 JSON 中没有类鉴别器字段来确定Range应实例化哪个子类。但即使它在那里,也还不够——我们Comparable还需要类鉴别器。

因此,我们需要一些启发式方法来实现基于内容的多态反序列化

为了这些启发式的简单性,我建议为此添加专用​​字段:(可以使用classDiscriminator属性type进行配置)并尊重。因此,应该调整序列化器:comparableType

@ExperimentalSerializationApi
inline fun <reified T : Range<K>, reified K : Comparable<K>> rangeSerializer(value: T): SerializationStrategy<T> =
    object : SerializationStrategy<T> {
        @Suppress("UNCHECKED_CAST")
        private val rangeSerializer = when (value) {
            is SimpleRange<*> -> serializer<SimpleRange<K>>()
            is MultiRange<*> -> serializer<MultiRange<K>>()
            else -> throw NotImplementedError() // still required even for sealed interfaces, KT-21908
        } as KSerializer<T>

        override val descriptor = rangeSerializer.descriptor

        override fun serialize(encoder: Encoder, value: T) = with(encoder as JsonEncoder) {
            val jsonElement = json.encodeToJsonElement(rangeSerializer, value)
            encodeJsonElement(transformSerialize(jsonElement, json))
        }

        private fun transformSerialize(element: JsonElement, json: Json) = JsonObject(element.jsonObject.toMutableMap().also {
            val typeSerialName = value::class.findAnnotation<SerialName>()?.value ?: value::class.simpleName!!
            it[json.configuration.classDiscriminator] = JsonPrimitive(typeSerialName)
            it["comparableType"] = JsonPrimitive(K::class.simpleName)
        })
    }
Run Code Online (Sandbox Code Playgroud)

现在,可以声明尊重的多态反序列化器:

@ExperimentalSerializationApi
@ExperimentalStdlibApi
object RangeDeserializer : DeserializationStrategy<Range<*>> {
    private val comparableSerializers = buildMap {
        registerSerializerFor<Int>()
        registerSerializerFor<Double>()
        registerSerializerFor<String>()
    }

    @InternalSerializationApi
    override val descriptor = buildSerialDescriptor("RangeDeserializer", PolymorphicKind.SEALED)

    override fun deserialize(decoder: Decoder): Range<*> = with(decoder as JsonDecoder) {
        val jsonObject = decodeJsonElement().jsonObject
        val deserializer = selectDeserializer(jsonObject, json)
        json.decodeFromJsonElement(deserializer, transformDeserialize(jsonObject, json))
    }

    private fun selectDeserializer(jsonObject: JsonObject, json: Json): DeserializationStrategy<out Range<*>> {
        val type = jsonObject[json.configuration.classDiscriminator]!!.jsonPrimitive.content
        val comparableType = jsonObject["comparableType"]!!.jsonPrimitive.content
        val comparableSerializer = comparableSerializers[comparableType]!!
        return when (type) {
            "range" -> SimpleRange.serializer(comparableSerializer)
            else -> MultiRange.serializer(comparableSerializer)
        }
    }

    //remove extra fields (which we introduced as heuristics for actual serializer selection) before further JSON processing to avoid requiring `ignoreUnknownKeys = true`
    private fun transformDeserialize(jsonObject: JsonObject, json: Json) = JsonObject(jsonObject.toMutableMap().also {
        it.remove(json.configuration.classDiscriminator)
        it.remove("comparableType")
    })
}

private inline fun <reified T : Comparable<T>> MutableMap<String, KSerializer<*>>.registerSerializerFor() =
        put(T::class.simpleName!!, serializer<T>())
Run Code Online (Sandbox Code Playgroud)

用法:

@ExperimentalSerializationApi
@ExperimentalStdlibApi
fun main() {
    val kotlinx = Json {
        serializersModule = module
    }
    val range1: SimpleRange<Int> = SimpleRange(1, 2)
    val encoded1 = kotlinx.encodeToString(rangeSerializer(range1), range1)
    println(encoded1) // {"min":1,"max":2,"type":"range","comparableType":"Int"}
    val range2: SimpleRange<Double> = SimpleRange(1.0, 2.0)
    val encoded2 = kotlinx.encodeToString(rangeSerializer(range2), range2)
    println(encoded2) // {"min":1.0,"max":2.0,"type":"range","comparableType":"Double"}
    val range3: Range<Double> = range2
    println(kotlinx.encodeToString(rangeSerializer(range3), range3)) // {"min":1.0,"max":2.0,"type":"range","comparableType":"Double"}

    val range1Decoded: Range<*> =
        kotlinx.decodeFromString(RangeDeserializer, encoded1) // SimpleRange(min=1, max=2)
    val range2Decoded: Range<*> =
        kotlinx.decodeFromString(RangeDeserializer, encoded2) // SimpleRange(min=1.0, max=2.0)
}
Run Code Online (Sandbox Code Playgroud)

如果您可以为实际序列化器选择提出更好的启发式(仅基于原始 JSON 的形状,而不引入额外的字段),那么您可以保留原始序列化器并享受更简洁的 JSON。