在忽略未知值的同时反序列化 Kotlin 枚举
Deserialize Kotlin enum while ignoring unknown values
我有一个枚举,我想使用 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)
忽略未知数。
问题
- 这是一个巨大的代码量。我希望有更多的枚举,我需要完全相同的行为,我真的不想为每个枚举复制粘贴它。我不知道如何使这种方法通用,因为这些对象不能通用,
@Serializable(with = ...)
需要一个编译时常量。
- 理想情况下,我希望将此行为封装在枚举本身中,以便反序列化
OperatingMode
的任何内容都将忽略未知数。
目前我认为唯一的方法是使用 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)
}
我找到了一种方法来重构我的问题中的方法以避免大部分代码重复。
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)
N.B. 这可能只适用于枚举等基本类型。如果您尝试将 SafeSerializer
与复杂类型一起使用,解码器可能会在解析 JSON 数组或对象等结构的过程中抛出异常。捕获该错误将使解码器处于无效状态。一种选择是更改 deserialize
以首先解码通用 JSON 元素而不捕获错误,然后 然后 在捕获时将其解码为 T
,但这是以将序列化器与 JSON.
紧密耦合为代价
如果 kotlinx.serialization 有不那么贪婪的解析器在抛出之前尝试完全消耗元素,这将不是问题。
我有一个枚举,我想使用 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)
忽略未知数。
问题
- 这是一个巨大的代码量。我希望有更多的枚举,我需要完全相同的行为,我真的不想为每个枚举复制粘贴它。我不知道如何使这种方法通用,因为这些对象不能通用,
@Serializable(with = ...)
需要一个编译时常量。 - 理想情况下,我希望将此行为封装在枚举本身中,以便反序列化
OperatingMode
的任何内容都将忽略未知数。
目前我认为唯一的方法是使用 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)
}
我找到了一种方法来重构我的问题中的方法以避免大部分代码重复。
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)
N.B. 这可能只适用于枚举等基本类型。如果您尝试将 SafeSerializer
与复杂类型一起使用,解码器可能会在解析 JSON 数组或对象等结构的过程中抛出异常。捕获该错误将使解码器处于无效状态。一种选择是更改 deserialize
以首先解码通用 JSON 元素而不捕获错误,然后 然后 在捕获时将其解码为 T
,但这是以将序列化器与 JSON.
如果 kotlinx.serialization 有不那么贪婪的解析器在抛出之前尝试完全消耗元素,这将不是问题。