【问题标题】:Deserialize Kotlin enum while ignoring unknown values反序列化 Kotlin 枚举同时忽略未知值
【发布时间】:2021-09-24 12:15:57
【问题描述】:

我有一个枚举,我想使用 kotlinx.serialization 从 JSON 反序列化,同时忽略未知值。这是枚举

@Serializable
enum class OperatingMode {
    Off, On, Auto
}

我所说的忽略未知数的意思是,如果我在 JSON 对象中有一个或多个不在该枚举中的模式,它们应该被视为不存在:

{"foo":"bar","mode":"Banana"}
// same as
{"foo":"bar"}

{"modes":["Off","On","Banana"]}
// same as
{"modes":["Off","On"]}

我通过编写自定义序列化程序来实现这一点,但对于这样一个简单的任务来说似乎相当冗长

internal object OperatingModeSafeSerializer : KSerializer<OperatingMode?> {
    override val descriptor = PrimitiveSerialDescriptor("OperatingMode", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: OperatingMode?) {
        // safe because @Serializable skips null fields
        encoder.encodeString(value!!.name)
    }

    override fun deserialize(decoder: Decoder): OperatingMode? {
        val string = decoder.decodeString()
        return try {
            OperatingMode.valueOf(string)
        } catch (_: Exception) {
            null
        }
    }
}

internal object OperatingModeSafeListSerializer: KSerializer<List<OperatingMode>> {
    private val delegateSerializer = ListSerializer(OperatingModeSafeSerializer)
    override val descriptor = delegateSerializer.descriptor

    override fun deserialize(decoder: Decoder): List<OperatingMode> {
        return decoder.decodeSerializableValue(delegateSerializer).filterNotNull()
    }

    override fun serialize(encoder: Encoder, value: List<OperatingMode>) {
        encoder.encodeSerializableValue(delegateSerializer, value)
    }
}

然后在每个反序列化OperatingMode的对象中我可以添加

@Serializable(with = OperatingModeSafeSerializer::class) // or
@Serializable(with = OperatingModeSafeListSerializer::class)

忽略未知数。

问题

  1. 这是大量的代码。我希望有更多的枚举,我需要完全相同的行为,我真的不想为每个枚举复制粘贴。我不知道如何使这种方法通用,因为这些对象不能通用,@Serializable(with = ...) 需要一个编译时常量。
  2. 理想情况下,我希望将此行为封装在枚举本身中,这样任何反序列化 OperatingMode 的操作都会忽略未知数。

【问题讨论】:

    标签: kotlin serialization enums


    【解决方案1】:

    目前我认为唯一的方法是使用 coerceInputValues 选项,枚举字段的默认值为 null,如下例所示:

    @Serializable
    enum class OperatingMode {
        Off, On, Auto
    }
    
    @Serializable
    data class Foo(val name: String, val mode: OperatingMode? = null)
    
    private val jsonFormat = Json {coerceInputValues = true}
    
    fun main() {
        val jsonString = """{"name":"ignored","mode":"AnotherOption"}"""
        val foo = jsonFormat.decodeFromString<Foo>(jsonString)
        println(foo)
    }
    

    【讨论】:

    • 嗯,这要短得多,但它不能解决问题 #2,因为我必须确保正确配置每个解码器以避免异常。
    • 我也看不到如何将它与List&lt;OperatingMode&gt; 一起使用,因为我不希望它回退到默认值,我希望它解析整个列表并仅忽略某些元素
    【解决方案2】:

    我找到了一种方法来重构我的问题中的方法以避免大部分代码重复。

    open class SafeSerializer<T>(
        private val serializer: KSerializer<T>
    ): KSerializer<T?> {
        override val descriptor = serializer.descriptor
    
        // safe because @Serializable skips null fields
        override fun serialize(encoder: Encoder, value: T?) = encoder.encodeSerializableValue(serializer, value!!)
    
        override fun deserialize(decoder: Decoder): T? = try {
            decoder.decodeSerializableValue(serializer)
        } catch (_: Exception) {
            null
        }
    }
        
    open class NonNullListSerializer<T>(
        serializer: KSerializer<T?>
    ): KSerializer<List<T>> {
        private val delegateSerializer = ListSerializer(serializer)
        override val descriptor = delegateSerializer.descriptor
    
        override fun serialize(encoder: Encoder, value: List<T>) = encoder.encodeSerializableValue(delegateSerializer, value)
    
        override fun deserialize(decoder: Decoder): List<T> = decoder.decodeSerializableValue(delegateSerializer).filterNotNull()
    }
    

    然后对于你想以这种方式反序列化的每个枚举,你只需声明

    object OperatingModeSafeSerializer: SafeSerializer<OperatingMode>(OperatingMode.serializer())
    object OperatingModeSafeListSerializer: NonNullListSerializer<OperatingMode>(OperatingModeSafeSerializer)
    

    注意这可能只适用于枚举等基本类型。如果您尝试将 SafeSerializer 与复杂类型一起使用,解码器可能会在解析 JSON 数组或对象等结构的过程中抛出异常。捕获该错误将使解码器处于无效状态。一种选择是将 deserialize 更改为首先解码通用 JSON 元素而不捕获错误,然后在捕获时将其解码为 T,但这是以将序列化程序与 JSON 紧密耦合为代价的。

    如果 kotlinx.serialization 没有那么贪婪的解析器尝试在抛出之前完全消耗元素,这将不是问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-16
      • 2019-07-29
      • 2015-05-23
      相关资源
      最近更新 更多