You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中arrow-kt NonEmptyList通用序列化器实现难题

解决Arrow-Kt NonEmptyList的序列化/反序列化问题

核心问题分析

你的代码失败原因是泛型类型擦除:运行时无法获取泛型参数T的具体类型,因此直接调用T::class.serializer()会报错。要解决这个问题,需要显式传递或从序列化上下文中获取T的序列化器。

方案1:显式传入元素序列化器(推荐)

修改序列化器,将T的KSerializer作为构造参数传入,同时通过伴生对象的具体化方法简化调用:

import arrow.core.NonEmptyList
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

class NonEmptyListSerializer<T : Serializable>(
    private val elementSerializer: KSerializer<T>
) : KSerializer<NonEmptyList<T>> {
    private val delegatedListSerializer = ListSerializer(elementSerializer)

    @OptIn(ExperimentalSerializationApi::class)
    override val descriptor: SerialDescriptor = 
        SerialDescriptor("NonEmptyList", delegatedListSerializer.descriptor)

    override fun serialize(encoder: Encoder, value: NonEmptyList<T>) {
        // 将NonEmptyList转为普通List后序列化
        encoder.encodeSerializableValue(delegatedListSerializer, value.toList())
    }

    override fun deserialize(decoder: Decoder): NonEmptyList<T> {
        val list = decoder.decodeSerializableValue(delegatedListSerializer)
        // 避免使用unsafe方法,空列表时抛出明确异常
        return NonEmptyList.fromList(list)
            ?: throw SerializationException("Cannot deserialize empty list as NonEmptyList")
    }

    // 伴生对象提供具体化方法,自动获取T的序列化器
    companion object {
        inline fun <reified T : Serializable> create(): KSerializer<NonEmptyList<T>> =
            NonEmptyListSerializer(T::class.serializer())
    }
}

使用方式

在需要序列化的类中指定序列化器:

@Serializable
data class MyData(
    @Serializable(with = NonEmptyListSerializer::class)
    val items: NonEmptyList<String>
)

// 或者在创建序列化器时直接调用create方法
val serializer = NonEmptyListSerializer.create<String>()

方案2:从序列化上下文获取序列化器

如果你的项目使用了上下文序列化(比如多态类型),可以从serializersModule中获取T的序列化器:

import arrow.core.NonEmptyList
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

class NonEmptyListSerializer<T : Serializable> : KSerializer<NonEmptyList<T>> {
    @OptIn(ExperimentalSerializationApi::class)
    override val descriptor: SerialDescriptor = 
        SerialDescriptor("NonEmptyList", ListSerializer(PolymorphicSerializer(Serializable::class)).descriptor)

    override fun serialize(encoder: Encoder, value: NonEmptyList<T>) {
        val elementSerializer = encoder.serializersModule.getContextual(T::class)
            ?: error("No serializer registered for type ${T::class.simpleName}")
        encoder.encodeSerializableValue(ListSerializer(elementSerializer), value.toList())
    }

    override fun deserialize(decoder: Decoder): NonEmptyList<T> {
        val elementSerializer = decoder.serializersModule.getContextual(T::class)
            ?: error("No serializer registered for type ${T::class.simpleName}")
        val list = decoder.decodeSerializableValue(ListSerializer(elementSerializer))
        return NonEmptyList.fromList(list)
            ?: throw SerializationException("Cannot deserialize empty list as NonEmptyList")
    }

    companion object : KSerializer<NonEmptyList<*>> by NonEmptyListSerializer<Serializable>()
}

注意事项

  • 需要在SerializersModule中注册T的序列化器,或为T添加@Contextual注解。
  • 此方案更适合多态场景,普通场景推荐方案1。

关键优化点

  • 避免使用fromListUnsafe:该方法在列表为空时会抛出未经检查的异常,改用fromList并抛出SerializationException,符合序列化框架的异常规范。
  • 显式传递序列化器:从根源解决泛型擦除问题,确保运行时能正确获取T的序列化逻辑。

内容的提问来源于stack exchange,提问作者Ulrich Schuster

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 05:50:49