Kotlin如何实现可观察且支持序列化的多平台兼容属性?
问题根源说明
Kotlinx Serialization 默认会忽略委托属性,因为委托属性的实际值存储在委托类的实例中,而非当前VO类的直接字段,序列化器默认不会通过getter/setter访问属性值,因此会出现序列化结果为空的情况。
可行解决方案(按优先级排序)
方案1:委托属性加@SerialName显式标注(最易实现,全平台兼容)
不需要修改现有委托逻辑,只需要给需要序列化的委托属性加上@SerialName注解,显式告诉序列化器要处理这个属性,序列化器会自动通过getter取值、setter设值,完全兼容JVM、JS、Android、Native全平台。
示例代码:
import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.encodeToString import kotlin.reflect.KProperty interface Observable { fun change(message: String) { println("changing $message") } @Suppress("ClassName") class default<T>(defaultValue: T) { private var value: T = defaultValue operator fun getValue(observable: Observable, property: KProperty<*>): T = value operator fun setValue(observable: Observable, property: KProperty<*>, value: T) { this.value = value observable.change(property.name) } } } @Serializable class BlahVO : Observable { // 加@SerialName显式指定序列化字段名即可正常序列化 @SerialName("value1") var value1: String by Observable.default("value1") fun toJson(): String = Json.encodeToString(serializer(), this) }
测试执行输出和手动写setter的效果完全一致:
changing value1 {"value1":"test2"}
如果不需要反序列化时触发变更监听,可以加个简单的开关控制:
interface Observable { var isDeserializing: Boolean fun change(message: String) { if (!isDeserializing) println("changing $message") } }
反序列化时把isDeserializing设为true,结束后恢复为false即可。
方案2:KSP生成样板代码(性能最优,无运行时开销)
如果属性数量多,不想每个都加注解,可以自定义轻量的KSP(Kotlin符号处理)注解处理器,编译期自动给标注了自定义注解的属性生成带变更监听的setter,生成的代码和你手动写的完全一致,没有任何运行时开销,也完全兼容序列化。
实现步骤:
- 定义源码级注解:
@Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.SOURCE) annotation class Watchable
- 实现KSP处理器,遍历所有标注了
@Watchable的属性,自动生成带change(property.name)调用的setter逻辑 - 业务代码写法极简:
@Serializable class BlahVO : Observable { @Watchable var value1: String = "" }
内容的提问来源于stack exchange,提问作者Jan Vladimir Mostert
相关产品推荐
相关产品推荐

