L'implementazione di proprietà osservabili che possono anche serializzare in Kotlin

0

Domanda

Sto cercando di costruire una classe in cui certi valori sono Osservabili, ma anche Serializable.

Questo, ovviamente, le opere e la serializzazione funziona, ma è molto standard-pesante dover aggiungere un setter per ogni singolo campo e manualmente dover chiamare change(...) all'interno di ogni 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" }) correttamente uscite

changing value2
{"value2":"test2"}

Ho cercato di introdurre i partecipanti:

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" }) scattare correttamente il rilevamento delle modifiche, ma non serializzare:

changing value1
{}

Se vado da Osservabile 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)
    }
}

il risultato è lo stesso:

changing blah!
{}

Allo stesso modo per i Delegati.vetoable

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

uscite:

changing value4
{}

I delegati non sembra di lavorare con Kotlin Serializzazione

Quali altre opzioni ci sono per osservare una struttura a modifiche senza rompere la sua serializzazione che funziona anche su altre piattaforme (KotlinJS, KotlinJVM, Android, ...)?

1

Migliore risposta

2

La serializzazione e la Deserializzazione di Kotlin Delegati non è supportato da kotlinx.serialization ora come ora.
C'è una questione aperta #1578 su GitHub per quanto riguarda questa funzione.

Secondo il problema, è possibile creare una intermedia di trasferimento dei dati oggetto, che viene serializzata invece dell'oggetto originale. Inoltre, si potrebbe scrivere un serializzatore personalizzato per supportare la serializzazione di Kotlin Delegati, che sembra essere ancora più standard, quindi la scrittura personalizzato getter e setter, come proposto nella domanda.


Oggetto Di Trasferimento Di Dati

Attraverso la mappatura originale oggetto di un semplice oggetto di trasferimento di dati senza delegati, è possibile utilizzare l'impostazione predefinita meccanismi di serializzazione. Questo ha anche il piacevole effetto collaterale di pulire il vostro modello di dati di classi quadro annotazioni specifiche, come @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())
}

In tal modo si ottiene il seguente risultato:

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

Tipo di dati personalizzati

Se la modifica del tipo di dati è un'opzione, si potrebbe scrivere un avvolgimento di classe che ottiene (de)serializzato in modo trasparente. Qualcosa lungo le linee delle seguenti potrebbe funzionare.

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

Da cui si ricava il seguente risultato:

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

Tuttavia, la perdita di informazioni circa la proprietà è cambiata, non hanno facile accesso al campo del nome. Inoltre bisogna cambiare le strutture di dati, come detto sopra, potrebbe non essere auspicabile o possibile. Inoltre, questo lavoro solo per le Stringhe per ora, anche se uno potrebbe rendere più generico, anche se. Inoltre, questo richiede un sacco di boilerplate per iniziare con. Sul sito di chiamata tuttavia, basta avvolgere il valore attuale in una chiamata a obs. Ho usato i seguenti standard per farlo funzionare.

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

Io attualmente seguono un approccio simile, ma ci si sente come potrebbe essere migliore. Sono andato un ulteriore passo per la creazione di un metodo monitoredString che restituisce un MonitoredString e poiché la funzione ha accesso a questo, non devo passare il onChange, posso solo collegarlo all'OnChange da questo. Il rovescio della medaglia di avere un Osservabili "stato" della classe e quindi un trasferimento di dati di classe che può essere serializzato è la duplicazione dei campi del modello. Sembra l'unica soluzione che consente di ottenere ciò che voglio fare, è quello di annotare con @Qualcosa e quindi generare il boilerplate utilizzo di KSP.
Jan Vladimir Mostert

In altre lingue

Questa pagina è in altre lingue

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