简体   繁体   English

实现也可以在 Kotlin 中序列化的可观察属性

[英]Implementing observable properties that can also serialize in Kotlin

I'm trying to build a class where certain values are Observable but also Serializable.我正在尝试构建一个 class ,其中某些值是可观察的但也是可序列化的。

This obviously works and the serialization works, but it's very boilerplate-heavy having to add a setter for every single field and manually having to call change(...) inside each setter:这显然有效并且序列化有效,但是必须为每个字段添加一个 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" }) correctly outputs println(BlahVO().apply { value2 = "test2" })正确输出

changing value2
{"value2":"test2"}

I've tried introducing Delegates:我试过介绍代表:

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" }) correctly triggers change detection, but it doesn't serialize: println(BlahVO().apply { value1 = "test1" })正确触发更改检测,但它不会序列化:

changing value1
{}

If I go from Observable to ReadWriteProperty,如果我从 Observable 到 ReadWriteProperty 的 go,

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)
    }
}

the result is the same:结果是一样的:

changing blah!
{}

Similarly for Delegates.vetoable对于 Delegates.vetoable 也是如此

var value4: String by Delegates.vetoable("value4", {
        property: KProperty<*>, oldstring: String, newString: String ->
    this.change(property.name)
    true
})

outputs:输出:

changing value4
{}

Delegates just doesn't seem to work with Kotlin Serialization代表似乎不适用于 Kotlin 序列化

What other options are there to observe a property's changes without breaking its serialization that will also work on other platforms (KotlinJS, KotlinJVM, Android, ...)?还有哪些其他选项可以在不破坏其序列化的情况下观察属性的变化,这些序列化也可以在其他平台(KotlinJS、KotlinJVM、Android、...)上运行?

Serialization and Deserialization of Kotlin Delegates is not supported by kotlinx.serialization as of now. kotlinx.serialization 目前不支持kotlinx.serialization Delegates 的序列化和反序列化。
There is an open issue #1578 on GitHub regarding this feature. GitHub 上有一个关于此功能的未解决问题 #1578

According to the issue you can create an intermediate data-transfer object, which gets serialized instead of the original object.根据问题,您可以创建一个中间数据传输 object,它被序列化而不是原始 object。 Also you could write a custom serializer to support the serialization of Kotlin Delegates, which seems to be even more boilerplate, then writing custom getters and setters, as proposed in the question.您也可以编写一个自定义序列化程序来支持 Kotlin 代表的序列化,这似乎是更多样板,然后编写自定义 getter 和 setter,如问题中所建议的那样。


Data Transfer Object数据传输 Object

By mapping your original object to a simple data transfer object without delegates, you can utilize the default serialization mechanisms.通过将原始 object 映射到没有委托的简单数据传输 object,您可以利用默认的序列化机制。 This also has the nice side effect to cleanse your data model classes from framework specific annotations, such as @Serializable .这还有一个很好的副作用,可以从框架特定的注释中清除您的数据 model 类,例如@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())
}

This yields the following result:这会产生以下结果:

{"observedProperty":"initial"}
Hey, I changed "observedProperty" from "initial" to "changed"!
{"observedProperty":"changed"}

Custom data type自定义数据类型

If changing the data type is an option, you could write a wrapping class which gets (de)serialized transparently.如果更改数据类型是一个选项,您可以编写一个包装 class 透明地进行(反)序列化。 Something along the lines of the following might work.以下内容可能会起作用。

@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())
}

Which yields the following result:这会产生以下结果:

{"monitoredProperty":"obsDefault"}
I changed from "obsDefault" to "obsChanged"!
{"monitoredProperty":"obsChanged"}

You however lose information about which property changed, as you don't have easy access to the field name.但是,您会丢失有关更改了哪个属性的信息,因为您无法轻松访问字段名称。 Also you have to change your data structures, as mentioned above and might not be desirable or even possible.此外,如上所述,您还必须更改数据结构,这可能是不可取的,甚至是不可能的。 In addition, this work only for Strings for now, even though one might make it more generic though.此外,目前这仅适用于字符串,尽管可能会使它更通用。 Also, this requires a lot of boilerplate to start with.此外,这需要大量的样板文件开始。 On the call site however, you just have to wrap the actual value in an call to obs .但是,在调用站点上,您只需将实际值包装在对obs的调用中。 I used the following boilerplate to get it to work.我使用以下样板来让它工作。

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)
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM