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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:52:03