实现也可以在 Kotlin 中序列化的可观察属性
Implementing observable properties that can also serialize in Kotlin
我正在尝试构建一个 class,其中某些值是可观察的,但也是可序列化的。
这显然有效并且序列化有效,但是必须为每个字段添加一个 setter 并且必须在每个 setter 中手动调用 change(...)
是非常繁重的样板文件:
interface Observable {
fun change(message: String) {
println("changing $message")
}
}
@Serializable
class BlahVO : Observable {
var value2: String = ""
set(value) {
field = value
change("value2")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value2 = "test2" })
正确输出
changing value2
{"value2":"test2"}
我试过介绍代表:
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 {
return value
}
operator fun setValue(observable: Observable, property: KProperty<*>, value: T) {
this.value = value
observable.change(property.name)
}
}
}
@Serializable
class BlahVO : Observable {
var value1: String by Observable.default("value1")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value1 = "test1" })
正确触发变更检测,但不序列化:
changing value1
{}
如果我从 Observable 转到 ReadWriteProperty,
interface Observable {
fun change(message: String) {
println("changing $message")
}
fun <T> look(defaultValue: T): ReadWriteProperty<Observable, T> {
return OP(defaultValue, this)
}
class OP<T>(defaultValue: T, val observable: Observable) : ObservableProperty<T>(defaultValue) {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
super.setValue(thisRef, property, value)
observable.change("blah!")
}
}
}
@Serializable
class BlahVO : Observable {
var value3: String by this.look("value3")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
结果是一样的:
changing blah!
{}
同样适用于 Delegates.vetoable
var value4: String by Delegates.vetoable("value4", {
property: KProperty<*>, oldstring: String, newString: String ->
this.change(property.name)
true
})
输出:
changing value4
{}
委托似乎不适用于 Kotlin 序列化
还有哪些其他选项可以在不破坏其序列化的情况下观察 属性 的变化,这些变化也适用于其他平台(KotlinJS、KotlinJVM、Android、...)?
截至目前,kotlinx.serialization
不支持 Kotlin 委托的序列化和反序列化。
GitHub 上有关于此功能的 open issue #1578。
根据问题,您可以创建一个中间数据传输对象,该对象将被序列化而不是原始对象。您也可以编写一个自定义序列化程序来支持 Kotlin 委托的序列化,这似乎是更多样板,然后按照问题中的建议编写自定义 getter 和 setter。
数据传输对象
通过将原始对象映射到没有委托的简单数据传输对象,您可以利用默认的序列化机制。
这也有很好的副作用,可以从框架特定的注释中清除数据模型 classes,例如 @Serializable
.
class DataModel {
var observedProperty: String by Delegates.observable("initial") { property, before, after ->
println("""Hey, I changed "${property.name}" from "$before" to "$after"!""")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this.toDto())
}
}
fun DataModel.toDto() = DataTransferObject(observedProperty)
@Serializable
class DataTransferObject(val observedProperty: String)
fun main() {
val data = DataModel()
println(data.toJson())
data.observedProperty = "changed"
println(data.toJson())
}
这会产生以下结果:
{"observedProperty":"initial"}
Hey, I changed "observedProperty" from "initial" to "changed"!
{"observedProperty":"changed"}
自定义数据类型
如果可以选择更改数据类型,您可以编写一个包装 class,它可以透明地进行(反)序列化。以下内容可能会起作用。
@Serializable
class ClassWithMonitoredString(val monitoredProperty: MonitoredString) {
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
fun main() {
val monitoredString = obs("obsDefault") { before, after ->
println("""I changed from "$before" to "$after"!""")
}
val data = ClassWithMonitoredString(monitoredString)
println(data.toJson())
data.monitoredProperty.value = "obsChanged"
println(data.toJson())
}
产生以下结果:
{"monitoredProperty":"obsDefault"}
I changed from "obsDefault" to "obsChanged"!
{"monitoredProperty":"obsChanged"}
但是您会丢失有关 属性 更改的信息,因为您无法轻松访问字段名称。如上所述,您还必须更改数据结构,这可能是不可取的,甚至是不可能的。此外,这目前仅适用于字符串,尽管可能会使它更通用。
此外,这需要大量样板文件才能开始。然而,在调用站点上,您只需将实际值包装在对 obs
的调用中。
我使用以下样板来让它工作。
typealias OnChange = (before: String, after: String) -> Unit
@Serializable(with = MonitoredStringSerializer::class)
class MonitoredString(initialValue: String, var onChange: OnChange?) {
var value: String = initialValue
set(value) {
onChange?.invoke(field, value)
field = value
}
}
fun obs(value: String, onChange: OnChange? = null) = MonitoredString(value, onChange)
object MonitoredStringSerializer : KSerializer<MonitoredString> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MonitoredString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: MonitoredString) {
encoder.encodeString(value.value)
}
override fun deserialize(decoder: Decoder): MonitoredString {
return MonitoredString(decoder.decodeString(), null)
}
}
我正在尝试构建一个 class,其中某些值是可观察的,但也是可序列化的。
这显然有效并且序列化有效,但是必须为每个字段添加一个 setter 并且必须在每个 setter 中手动调用 change(...)
是非常繁重的样板文件:
interface Observable {
fun change(message: String) {
println("changing $message")
}
}
@Serializable
class BlahVO : Observable {
var value2: String = ""
set(value) {
field = value
change("value2")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value2 = "test2" })
正确输出
changing value2
{"value2":"test2"}
我试过介绍代表:
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 {
return value
}
operator fun setValue(observable: Observable, property: KProperty<*>, value: T) {
this.value = value
observable.change(property.name)
}
}
}
@Serializable
class BlahVO : Observable {
var value1: String by Observable.default("value1")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value1 = "test1" })
正确触发变更检测,但不序列化:
changing value1
{}
如果我从 Observable 转到 ReadWriteProperty,
interface Observable {
fun change(message: String) {
println("changing $message")
}
fun <T> look(defaultValue: T): ReadWriteProperty<Observable, T> {
return OP(defaultValue, this)
}
class OP<T>(defaultValue: T, val observable: Observable) : ObservableProperty<T>(defaultValue) {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
super.setValue(thisRef, property, value)
observable.change("blah!")
}
}
}
@Serializable
class BlahVO : Observable {
var value3: String by this.look("value3")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
结果是一样的:
changing blah!
{}
同样适用于 Delegates.vetoable
var value4: String by Delegates.vetoable("value4", {
property: KProperty<*>, oldstring: String, newString: String ->
this.change(property.name)
true
})
输出:
changing value4
{}
委托似乎不适用于 Kotlin 序列化
还有哪些其他选项可以在不破坏其序列化的情况下观察 属性 的变化,这些变化也适用于其他平台(KotlinJS、KotlinJVM、Android、...)?
截至目前,kotlinx.serialization
不支持 Kotlin 委托的序列化和反序列化。
GitHub 上有关于此功能的 open issue #1578。
根据问题,您可以创建一个中间数据传输对象,该对象将被序列化而不是原始对象。您也可以编写一个自定义序列化程序来支持 Kotlin 委托的序列化,这似乎是更多样板,然后按照问题中的建议编写自定义 getter 和 setter。
数据传输对象
通过将原始对象映射到没有委托的简单数据传输对象,您可以利用默认的序列化机制。
这也有很好的副作用,可以从框架特定的注释中清除数据模型 classes,例如 @Serializable
.
class DataModel {
var observedProperty: String by Delegates.observable("initial") { property, before, after ->
println("""Hey, I changed "${property.name}" from "$before" to "$after"!""")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this.toDto())
}
}
fun DataModel.toDto() = DataTransferObject(observedProperty)
@Serializable
class DataTransferObject(val observedProperty: String)
fun main() {
val data = DataModel()
println(data.toJson())
data.observedProperty = "changed"
println(data.toJson())
}
这会产生以下结果:
{"observedProperty":"initial"}
Hey, I changed "observedProperty" from "initial" to "changed"!
{"observedProperty":"changed"}
自定义数据类型
如果可以选择更改数据类型,您可以编写一个包装 class,它可以透明地进行(反)序列化。以下内容可能会起作用。
@Serializable
class ClassWithMonitoredString(val monitoredProperty: MonitoredString) {
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
fun main() {
val monitoredString = obs("obsDefault") { before, after ->
println("""I changed from "$before" to "$after"!""")
}
val data = ClassWithMonitoredString(monitoredString)
println(data.toJson())
data.monitoredProperty.value = "obsChanged"
println(data.toJson())
}
产生以下结果:
{"monitoredProperty":"obsDefault"}
I changed from "obsDefault" to "obsChanged"!
{"monitoredProperty":"obsChanged"}
但是您会丢失有关 属性 更改的信息,因为您无法轻松访问字段名称。如上所述,您还必须更改数据结构,这可能是不可取的,甚至是不可能的。此外,这目前仅适用于字符串,尽管可能会使它更通用。
此外,这需要大量样板文件才能开始。然而,在调用站点上,您只需将实际值包装在对 obs
的调用中。
我使用以下样板来让它工作。
typealias OnChange = (before: String, after: String) -> Unit
@Serializable(with = MonitoredStringSerializer::class)
class MonitoredString(initialValue: String, var onChange: OnChange?) {
var value: String = initialValue
set(value) {
onChange?.invoke(field, value)
field = value
}
}
fun obs(value: String, onChange: OnChange? = null) = MonitoredString(value, onChange)
object MonitoredStringSerializer : KSerializer<MonitoredString> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MonitoredString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: MonitoredString) {
encoder.encodeString(value.value)
}
override fun deserialize(decoder: Decoder): MonitoredString {
return MonitoredString(decoder.decodeString(), null)
}
}