Prováděcí pozorovatelné vlastnosti, které lze také serializovat v Kotlin

0

Otázka

Snažím se vytvořit třídu, kde jsou některé hodnoty Pozorovatelné, ale také Serializable.

To samozřejmě funguje a serializace funguje, ale je to velmi často používaný-těžký museli přidat setter pro každé pole a ručně museli volat change(...) uvnitř každé setr:

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" }) správně výstupy

changing value2
{"value2":"test2"}

Snažil jsem se zavést Delegátů:

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" }) správně spouští detekci změn, ale to neznamená, serializovat:

changing value1
{}

Když jsem se jít z Pozorovatelných 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)
    }
}

výsledek je stejný:

changing blah!
{}

Podobně pro Delegáty.vetoable

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

výstupy:

changing value4
{}

Delegáti prostě nezdá pracovat s Kotlin Serializace

Jaké jsou další možnosti pozorovat majetek změny, aniž by to ohrozilo jeho serializace, že bude také pracovat na jiných platformách (KotlinJS, KotlinJVM, Android, ...)?

1

Nejlepší odpověď

2

Serializace a Deserializace Kotlin Delegátů není podporován kotlinx.serialization jak nyní.
Je otevřenou otázkou #1578 na GitHub týkající se této funkce.

Podle problému, můžete vytvořit zprostředkující přenos dat objekt, který se dostane na pokračování namísto původního objektu. Také můžete napsat vlastní serializátor pro podporu serializace Kotlin Delegátů, která se zdá být ještě více, často používaný, a pak psát vlastní getter a setter, jak je navrženo v otázce.


Přenos Dat Objektu

Mapování svou původní objekt na jednoduchý přenos dat objekt, aniž delegátů, můžete využít výchozí serializace mechanismy. To také má pěkný vedlejší efekt očistit vaše data model třídy z rámce konkrétní popisy, např. @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())
}

To přináší následující výsledek:

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

Vlastní datový typ

Pokud změníte typ dat je možnost, že byste mohl napsat balicí třídy, která se dostane (de)serializovat transparentně. Něco v duchu následující by mohlo fungovat.

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

Což dává následující výsledek:

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

Ty se však ztrácí informace o tom, které nemovitosti se změnila, protože nemáte snadný přístup do pole název. Také budete muset změnit své datové struktury, jak je uvedeno výše a nemusí být žádoucí nebo dokonce možné. Kromě toho, tato práce pouze pro Struny pro teď, i když jeden by mohl dělat to více generic. Také, to vyžaduje hodně často používaný pro začátek. Na volání stránky, nicméně, budete muset zabalit skutečné hodnoty při volání obs. Použil jsem následující často používaný, aby si to do práce.

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)
    }
}
2021-11-24 18:19:41

I v současné době následovat podobný přístup, ale je to pocit, jako by to mohlo být lepší. Šel jsem o krok dále vytvoření metody, monitoredString která vrací MonitoredString a protože funkce má přístup k tomuto, nemám projít onChange, mohu jen odkaz, aby to OnChange. Nevýhodou s Pozorovatelný "stát" třídy a pak přenos dat třídy, které lze serializovat je duplikace modelu pole. Zdá se, že jediný dobrý řešení, který dosahuje, co chci dělat, je komentovat s @Něco, a pak generovat standardní použití KSP.
Jan Vladimir Mostert

V jiných jazycích

Tato stránka je v jiných jazycích

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................