La implementación de propiedades observables que también puede serializar en Kotlin

0

Pregunta

Estoy tratando de construir una clase en la que ciertos valores son Observables, sino también Serializable.

Obviamente, esto funciona y la serialización de las obras, pero es muy repetitivo, pesado tener que agregar un setter para cada campo y manualmente tener que llamar change(...) dentro de cada 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" }) correctamente los productos

changing value2
{"value2":"test2"}

He intentado introducir a los Delegados:

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" }) activa correctamente la detección de cambio, pero no serializar:

changing value1
{}

Si me voy de Observables a 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)
    }
}

el resultado es el mismo:

changing blah!
{}

Del mismo modo para los Delegados.vetoable

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

salidas:

changing value4
{}

Los delegados simplemente no parece funcionar con Kotlin Serialización

¿Qué otras opciones existen para observar de una propiedad, los cambios sin romper su serialización que también funciona en otras plataformas (KotlinJS, KotlinJVM, Android, ...)?

1

Mejor respuesta

2

La serialización y Deserialización de Kotlin Delegados no es compatible con kotlinx.serialization a partir de ahora.
Hay un tema abierto #1578 en GitHub con respecto a esta característica.

De acuerdo con el tema que usted puede crear un intermedio objeto de transferencia de datos, que se serializa en lugar del objeto original. También se podría escribir un serializador personalizado de apoyo para la serialización de Kotlin Delegados, que parece ser aún más repetitivo, a continuación, escribir personalizado getters y setters, como el propuesto en la pregunta.


Transferencia De Datos De Objeto

Mediante la asignación de su objeto original a una simple transferencia de datos objeto sin delegados, usted puede utilizar el valor predeterminado de la serialización de los mecanismos. Esto también tiene el efecto paralelo para limpiar su modelo de datos de clases de marco anotaciones específicas, tales como @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())
}

Este se obtiene el siguiente resultado:

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

Tipo de datos personalizado

Si se cambia el tipo de datos es una opción, podría escribir una envoltura de clase que se obtiene de (de)serializado de forma transparente. A lo largo de las líneas de la siguiente podría funcionar.

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

El cual arroja el siguiente resultado:

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

Sin embargo, perder la información acerca de que la propiedad ha cambiado, ya que no tienen fácil acceso al nombre del campo. También tiene que cambiar sus estructuras de datos, como se mencionó anteriormente y puede no ser deseable ni posible. Además, este trabajo sólo para las Cadenas de ahora, incluso a pesar de que uno podría hacer más genérico, aunque. También, esto requiere una gran cantidad de repetitivo para empezar. En el sitio de llamada, sin embargo, sólo tiene que ajustar el valor real de una llamada a obs. He utilizado el siguiente texto modelo para conseguir que funcione.

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

Yo actualmente sigue un enfoque similar, pero se siente como que podría ser mejor. He ido un paso más allá creando un método monitoredString que devuelve un MonitoredString y ya que la función tiene acceso a esta, no tengo que pasar el onChange, solo puedo vincularlo a OnChange de este. La desventaja de tener un Observable "estado" de la clase y, a continuación, una transferencia de datos de la clase que puede ser serializado es la duplicación de los campos del modelo. Parece ser la única buena solución que logre lo que quiero hacer, es de anotar con @Algo y luego generar el repetitivo uso de KSP.
Jan Vladimir Mostert

En otros idiomas

Esta página está en otros idiomas

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