La mise en œuvre de propriétés observables qui peuvent également sérialiser dans Kotlin

0

La question

Je suis en train de construire une classe où certaines valeurs sont Observables, mais aussi Serializable.

De toute évidence, cela fonctionne et la sérialisation fonctionne, mais c'est très passe-partout-lourd d'avoir à ajouter une définition pour chaque champ et manuellement à l'appel change(...) à l'intérieur de chaque setter:

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" }) correctement les résultats

changing value2
{"value2":"test2"}

J'ai essayé d'introduire des Délégués:

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" }) correctement déclenche la détection du changement, mais il n'a pas sérialiser:

changing value1
{}

Si je vais à partir d'Observables à 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)
    }
}

le résultat est le même:

changing blah!
{}

De même pour les Délégués.vetoable

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

sorties:

changing value4
{}

Les délégués ne semble tout simplement pas travailler avec Kotlin la Sérialisation

Quelles sont les autres options sont là pour observer une propriété de modifications sans casser sa sérialisation qui fonctionne également sur d'autres plates-formes (KotlinJS, KotlinJVM, Android, ...)?

1

La meilleure réponse

2

La sérialisation et la Désérialisation de Kotlin Délégués n'est pas pris en charge par kotlinx.serialization à partir de maintenant.
Il y a un problème ouvert #1578 sur GitHub concernant cette fonctionnalité.

Selon le problème, vous pouvez créer un intermédiaire de transfert de données de l'objet, qui est sérialisée au lieu de l'objet d'origine. Vous pouvez également écrire une coutume sérialiseur à l'appui de la sérialisation de Kotlin Délégués, qui semble être encore plus passe-partout, puis l'écriture personnalisée getters et setters, comme proposé dans la question.


Objet De Transfert De Données

Par la cartographie de votre objet original à un simple objet de transfert de données sans les délégués, vous pouvez utiliser la sérialisation par défaut des mécanismes. Cela a aussi le côté sympa de l'effet de nettoyer votre modèle de données des classes de cadre des annotations spécifiques, tels que @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())
}

Cela donne le résultat suivant:

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

Type de données personnalisé

Si vous souhaitez modifier le type de données est une option, vous pouvez écrire un emballage de classe qui obtient (de)sérialisé de manière transparente. Quelque chose le long des lignes de la suite qui pourrait fonctionner.

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

Ce qui donne le résultat suivant:

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

Cependant, vous perdez l'information à propos de la propriété qui a changé, comme vous n'avez pas un accès facile pour le nom du champ. Aussi, vous devez changer vos structures de données, comme mentionné ci-dessus et peut-être pas souhaitable, ni même possible. En outre, ce travail uniquement pour les Chaînes pour l'instant, même si l'on peut le rendre plus générique si. Aussi, cela exige beaucoup de passe-partout pour commencer. Sur le site d'appel, cependant, vous avez juste à envelopper la valeur réelle dans un appel à obs. J'ai utilisé le suivant passe-partout pour le faire fonctionner.

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

Je poursuis actuellement une approche similaire, mais il se sent comme il pourrait être mieux. J'ai franchi une étape supplémentaire en créant une méthode monitoredString qui renvoie une MonitoredString et puisque la fonction a accès à cet, je n'ai pas à passer le onChange, je peux juste le lien pour OnChange à partir de ce. L'inconvénient d'avoir une Observable "état" de la classe et puis un transfert de données de classe peut être sérialisé est la duplication du modèle de champs. Semble que la seule bonne solution qui permet d'obtenir ce que je veux faire, est d'annoter avec @quelque Chose et puis de générer le code à l'aide de KSP.
Jan Vladimir Mostert

Dans d'autres langues

Cette page est dans d'autres langues

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