Kotlin如何处理API返回空字符串而非null的非存在对象序列化?
Kotlinx Serialization:空字符串转Nullable属性的简化方案
问题场景
对接的API会返回空字符串而非null来表示属性不存在,示例JSON如下:
{ "id": 1, "child": { "foo": "bar" } // child存在 } { "id": 1, "child": "" // child不存在 }
但这与Kotlinx Serialization的可序列化数据类不兼容:
@Serializable data class Parent(val id: Int, val child: Child?) @Serializable data class Child(val foo: String)
当child字段为空字符串时,会抛出解码异常:
kotlinx.serialization.json.internal.JsonDecodingException: Expected class kotlinx.serialization.json.JsonObject (Kotlin reflection is not available) as the serialized body of com.mypackage.Parent, but had class kotlinx.serialization.json.JsonLiteral (Kotlin reflection is not available)
编写全类自定义序列化器可以解决问题,但这种方式繁琐,不适用于多属性数据类。我们需要一种能通过单个属性注解实现相同逻辑的方案。
方案1:轻量通用属性序列化器
编写一个通用的序列化器,专门处理“空字符串转null”的场景,通过@Serializable(with = ...)直接应用到目标属性上,无需编写全类序列化代码。
实现通用序列化器
import kotlinx.serialization.* import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* class NullOnEmptyStringSerializer<T : Any>(private val serializer: KSerializer<T>) : KSerializer<T?> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("NullOnEmptyString", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): T? { val jsonDecoder = decoder as JsonDecoder val element = jsonDecoder.decodeJsonElement() return when { element is JsonPrimitive && element.content.isEmpty() -> null element is JsonNull -> null else -> jsonDecoder.json.decodeFromJsonElement(serializer, element) } } override fun serialize(encoder: Encoder, value: T?) { if (value == null) { encoder.encodeNull() } else { serializer.serialize(encoder, value) } } }
应用到属性
@Serializable data class Parent( val id: Int, @Serializable(with = NullOnEmptyStringSerializer(Child.serializer())) val child: Child? ) @Serializable data class Child(val foo: String)
方案2:自定义注解简化使用
如果觉得方案1的写法不够简洁,可以结合反射封装自定义注解,实现类似@NullOnEmptyString的极简用法。
1. 添加反射依赖
在build.gradle.kts中添加与Kotlin版本匹配的反射依赖:
dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.20") // 版本需与项目Kotlin版本一致 }
2. 实现注解与序列化器
import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* import kotlin.reflect.KClass // 自定义注解 @Target(AnnotationTarget.PROPERTY) annotation class NullOnEmptyString // 注解对应的序列化器 class NullOnEmptyStringAnnotationSerializer<T : Any>(private val targetClass: KClass<T>) : KSerializer<T?> { private val serializer = targetClass.serializer() override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("NullOnEmptyStringAnnotation", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): T? { val jsonDecoder = decoder as JsonDecoder val element = jsonDecoder.decodeJsonElement() return when { element is JsonPrimitive && element.content.isEmpty() -> null element is JsonNull -> null else -> jsonDecoder.json.decodeFromJsonElement(serializer, element) } } override fun serialize(encoder: Encoder, value: T?) { if (value == null) { encoder.encodeNull() } else { serializer.serialize(encoder, value) } } } // 注册注解与序列化器的关联 @Serializer(forClass = NullOnEmptyString::class) object NullOnEmptyStringSerializerFactory : AnnotationSerializerFactory<NullOnEmptyString> { override fun <T> create(annotation: NullOnEmptyString, targetClass: KClass<T>): KSerializer<T>? { @Suppress("UNCHECKED_CAST") return NullOnEmptyStringAnnotationSerializer(targetClass as KClass<T & Any>) as KSerializer<T> } }
3. 极简使用方式
@Serializable data class Parent( val id: Int, @NullOnEmptyString val child: Child? ) @Serializable data class Child(val foo: String)
注意:方案2依赖Kotlin反射,若追求轻量无依赖,方案1是更优选择。
内容的提问来源于stack exchange,提问作者harrisonlee
相关产品推荐
相关产品推荐

