Serializing a Kotlin delegate with Gson - kotlin

I have a Kotlin object that I am trying serialize with Gson. A member that is setup as a delegate does not get serialized. The delegation works if I call it directly, as does the onChange callback, but Gson just ignores it.
Is there any way to get Gson to serialize this without writing a custom serializer?
Here is a simplified example of what I'm trying to do:
class MyDelegate() {
fun getProperty(): String {
return "myDelegate Property"
}
fun observableDelegate(onChange: () -> Unit): ReadWriteProperty<Any?, String> {
return object: ReadWriteProperty<Any?, String> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return getProperty()
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
TODO("not implemented")
}
}
}
}
class MyTest(delegate: MyDelegate, val property0: String = "property0" ) {
val property1 = "property1"
var property2 = "property2"
var property3: String by delegate.observableDelegate {
// onChange called
}
}
Testing it with:
#Test
fun testDelegate() {
val t1 = MyTest(MyDelegate())
val s1 = Gson().toJson(t1)
Assert.fail(s1)
}
Output:
{"property1":"property1","property2":"property2","property0":"property0"}

The property3 variable is not field backed. Thus Gson doesn't consider it as field in the Json serialization.
The GsonDesignDocument states for properties as such
Some Json libraries use the getters of a type to deduce the Json elements. We chose to use all fields (up the inheritance hierarchy) that are not transient, static, or synthetic. We did this because not all classes are written with suitably named getters. Moreover, getXXX or isXXX might be semantic rather than indicating properties.
So you might have to implement a custom (de)serializer for your needs.

Related

Kotlinx Serialization, inlining sealed class/interface [duplicate]

This question already has an answer here:
kotlinx deserialization: different types && scalar && arrays
(1 answer)
Closed 7 months ago.
With a structure similar to the following:
#Serializable
sealed class Parameters
#Serializable
data class StringContainer(val value: String): Parameters()
#Serializable
data class IntContainer(val value: Int): Parameters()
#Serializable
data class MapContainer(val value: Map<String, Parameters>): Parameters()
// more such as list, bool and other fairly (in the context) straight forward types
And the following container class:
#Serializable
data class PluginConfiguration(
// other value
val parameters: Parameters.MapContainer,
)
I want to reach a (de)serialization where the paramters are configured as a flexible json (or other) map, as one would usually expect:
{
"parameters": {
"key1": "String value",
"key2": 12,
"key3": {}
}
}
And so on. Effectively creating a flexible structure that is still structured enough to not be completely uncontrolled as Any would be. There's a fairly clearly defined (de)serialization, but I cannot figure how to do this.
I've tried reading the following
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serialization-guide.md
And I do have a hunch that a polymorphic serializer is needed, but so far I'm bumping in to either generic structures, which I believe is way overkill for my purpose or that it for some reason cannot find the serializer for my subclasses, when writing a custom serializer for Parameters.
Update
So using custom serializers combined with surrogate classes, most things are working. The current problem is when values are put into the map, I get a kotlin.IllegalStateException: Primitives cannot be serialized polymorphically with 'type' parameter. You can use 'JsonBuilder.useArrayPolymorphism' instead. Even when I enable array polymorphism this error arises
The answer with kotlinx deserialization: different types && scalar && arrays is basically the answer, and the one I will accept. However, for future use, the complete code to my solution is as follows:
Class hierarchy
#kotlinx.serialization.Serializable(with = ParametersSerializer::class)
sealed interface Parameters
#kotlinx.serialization.Serializable(with = IntContainerSerializer::class)
data class IntContainer(
val value: Int
) : Parameters
#kotlinx.serialization.Serializable(with = StringContainerSerializer::class)
data class StringContainer(
val value: String
) : Parameters
#kotlinx.serialization.Serializable(with = MapContainerSerializer::class)
data class MapContainer(
val value: Map<String, Parameters>
) : Parameters
#kotlinx.serialization.Serializable
data class PluginConfiguration(
val plugin: String,
val parameters: MenuRunnerTest.MapContainer
)
Serializers:
abstract class BaseParametersSerializer<T : Parameters> : KSerializer<T> {
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
override fun serialize(encoder: Encoder, value: T) {
fun toJsonElement(value: Parameters): JsonElement = when (value) {
is IntContainer -> JsonPrimitive(value.value)
is MapContainer -> JsonObject(
value.value.mapValues { toJsonElement(it.value) }
)
is StringContainer -> JsonPrimitive(value.value)
}
val sur = toJsonElement(value)
encoder.encodeSerializableValue(JsonElement.serializer(), sur)
}
override fun deserialize(decoder: Decoder): T {
with(decoder as JsonDecoder) {
val jsonElement = decodeJsonElement()
return deserializeJson(jsonElement)
}
}
abstract fun deserializeJson(jsonElement: JsonElement): T
}
object ParametersSerializer : BaseParametersSerializer<Parameters>() {
override fun deserializeJson(jsonElement: JsonElement): Parameters {
return when(jsonElement) {
is JsonPrimitive -> when {
jsonElement.isString -> StringContainerSerializer.deserializeJson(jsonElement)
else -> IntContainerSerializer.deserializeJson(jsonElement)
}
is JsonObject -> MapContainerSerializer.deserializeJson(jsonElement)
else -> throw IllegalArgumentException("Only ints, strings and strings are allowed here")
}
}
}
object StringContainerSerializer : BaseParametersSerializer<StringContainer>() {
override fun deserializeJson(jsonElement: JsonElement): StringContainer {
return when(jsonElement) {
is JsonPrimitive -> StringContainer(jsonElement.content)
else -> throw IllegalArgumentException("Only strings are allowed here")
}
}
}
object IntContainerSerializer : BaseParametersSerializer<IntContainer>() {
override fun deserializeJson(jsonElement: JsonElement): IntContainer {
return when (jsonElement) {
is JsonPrimitive -> IntContainer(jsonElement.int)
else -> throw IllegalArgumentException("Only ints are allowed here")
}
}
}
object MapContainerSerializer : BaseParametersSerializer<MapContainer>() {
override fun deserializeJson(jsonElement: JsonElement): MapContainer {
return when (jsonElement) {
is JsonObject -> MapContainer(jsonElement.mapValues { ParametersSerializer.deserializeJson(it.value) })
else -> throw IllegalArgumentException("Only maps are allowed here")
}
}
}
This structure should be expandable for lists, doubles and other structures, not included in the example :)

Implementing observable properties that can also serialize in Kotlin

I'm trying to build a class where certain values are Observable but also Serializable.
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:
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
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:
changing value1
{}
If I go from Observable to 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)
}
}
the result is the same:
changing blah!
{}
Similarly for 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
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, ...)?
Serialization and Deserialization of Kotlin Delegates is not supported by kotlinx.serialization as of now.
There is an open issue #1578 on GitHub regarding this feature.
According to the issue you can create an intermediate data-transfer object, which gets serialized instead of the original 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.
Data Transfer Object
By mapping your original object to a simple data transfer object without delegates, you can utilize the default serialization mechanisms.
This also has the nice side effect to cleanse your data model classes from framework specific annotations, such as #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. 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.
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)
}
}

Kotlin: generate a Factory by class

We're trying to do some generic processing in kotlin. Basically, for a given class, we want to get the related Builder object. i.a. for any object that extends a GenericObject, we want a Builder of that Object.
interface Builder<T : GenericObject>
object ConcreteBuilder: Builder<ConcreteObject>
We'd need a function that will return ConcreteBuilder from ConcreteObject
Our current implementation is a Map:
val map = mapOf<KClass<out GenericObject>, Builder<out GenericObject>>(
ConcreteObject::class to ConcreteBuilder
)
Then we can get it with:
inline fun <reified T : GenericObject> transform(...): T {
val builder = map[T::class] as Builder<T>
...
However this isn't very nice as:
we need an explicit cast to Builder<T>
the map has no notion of T, a key and a value could be related to different types.
Is there any better way to achieve it?
A wrapper for the map could be:
class BuilderMap {
private val map = mutableMapOf<KClass<out GenericObject>, Builder<out GenericObject>>()
fun <T: GenericObject> put(key: KClass<T>, value: Builder<T>) {
map[key] = value
}
operator fun <T: GenericObject> get(key: KClass<T>): Builder<T> {
return map[key] as Builder<T>
}
}
This hides the ugliness, while not completely removing it.
To use:
val builderMap = BuilderMap()
builderMap.put(ConcreteObject::class, ConcreteBuilder)
builderMap.put(BetonObject::class, BetonBuilder)
// builderMap.put(BetonObject::class, ConcreteBuilder) – will not compile
val builder = builderMap[T::class]

How do I make a delegate apply to all properties in a class?

I have a class, A, that needs to be marked as dirty anytime one of its properties is changed.
After reviewing the Kotlin docs, I know I need a delegate. So far I have:
abstract class CanBeDirty {
var isDirty = false
}
class A(
// properties getting set in constructor here
) : CanBeDirty {
var property1: String by DirtyDelegate()
var property2: Int by DirtyDelegate()
var property3: CustomObject by DirtyDelegate()
}
class DirtyDelegate() {
operator fun getValue(thisRef: CanBeDirty, property: KProperty<*>): Resource {
return valueOfTheProperty
}
operator fun setValue(thisRef: CanBeDirty, property: KProperty<*>, value: Any?) {
if (property != value) {
thisRef.isDirty = true
//set the value
}
else {
//don't set the value
}
}
}
I believe the lack of setting has something to do with vetoable() but the examples I see in Kotlin documentation don't really show me how to do this with a fully formed class Delegate (and I'm just not that up to speed on Kotlin syntax, honestly).
Your delegate class needs its own property to store the value it will return. And if you don't want to deal with uninitialized values, it should also have a constructor parameter for the initial value. You don't have to implement ReadWriteProperty, but it allows the IDE to autogenerate the correct signature for the two operator functions.
class DirtyDelegate<T>(initialValue: T): ReadWriteProperty<CanBeDirty, T> {
private var _value = initialValue
override fun getValue(thisRef: CanBeDirty, property: KProperty<*>): T {
return _value
}
override fun setValue(thisRef: CanBeDirty, property: KProperty<*>, value: T) {
if (_value != value) {
_value = value
thisRef.isDirty = true
}
}
}
Since this takes an initial value parameter, you have to pass it to the constructor:
class A: CanBeDirty() {
var property1: String by DirtyDelegate("")
var property2: Int by DirtyDelegate(0)
var property3: CustomObject by DirtyDelegate(CustomObject())
}
If you wanted to set an initial value based on something passed to the constructor, you could do:
class B(initialName: String): CanBeDirty() {
var name by DirtyDelegate(initialName)
}

Is there any way to declare a scope extension to third party library kotlin class?

I'm trying a program build with Jetbrain/Exposed as ORM library and TornadoFX which is a kotlin version wrapper of JavaFX as UI Framework.
There is a problem that an entity's class property is delegated by Exposed.
object Paintings: UUIDTable() {
...
val description = text("description").default("")
...
}
class Painting(id: EntityID<UUID>) : UUIDEntity(id) {
...
var description by Paintings.description
...
}
And I'm also want to create an property delegated to JavaFX's property like this
class Painting(id: EntityID<UUID>) : UUIDEntity(id) {
...
var description by property<String>()
fun descriptionProperty() = getProperty(Painting::description)
...
}
There is a conflict here,so I'm trying to build a own delegate class to wrapper two framework's delegated.Maybe build something which can wrap two frameworks's delegate though a infix function extension.
class Painting(id: EntityID<UUID>) : UUIDEntity(id) {
...
var description by Paintings.description notify property<String>()
fun descriptionProperty() = getProperty(Painting::description)
...
}
And here is the problem that the operator setValue and getValue for delegate from Exposed is declare in Entity class
open class Entity<ID:Comparable<ID>>(val id: EntityID<ID>) {
...
operator fun <T> Column<T>.getValue(o: Entity<ID>, desc: KProperty<*>): T =
lookup()
operator fun <T> Column<T>.setValue(o: Entity<ID>, desc: KProperty<*>, value: T) {...}
If I declare a wrapper class it just cannot access the delegate operator for Column which is scoped in Entity class
//Global Extension instead of scoped to `Entity`
infix fun <T> Column<T>.notify(fxProperty: PropertyDelegate<T>) {
return DelegateWrapper(this,fxProperty)
}
class DelegateWrapper<T>(val column: Column<T>, val fxProperty: PropertyDelegate<T>) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return column.getValue(...) <-cannot resolve getValue
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String)
{
...
}
}
As a work arround I guess I had to build a subclass of UUIDEntity and add those extension as member extension and nest class to make it work.
You can do that by limiting the type that the delegate can be used with to Entity.
This can be done by replacing thisRef: Any? with thisRef: Entity<ID> and calling the getValue extension in a thisRef.run { ... } block that provides an Entitiy<ID> receiver as follows:
class DelegateWrapper<T>(val column: Column<T>, val fxProperty: PropertyDelegate<T>) {
operator fun <ID : Comparable<ID>> getValue(
thisRef: Entity<ID>, // <-- here
property: KProperty<*>
): String =
thisRef.run { column.getValue(thisRef, property) }
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) = TODO()
}