Kotlin: single property with multiple setters of different types - kotlin

I'm trying to build a class that has a property of LocalDate type which has setters that accept different types: LocalDate or String. In case of LocalDate, the value gets assigned directly, in case of String, it gets parsed and then assigned.
In Java, I just need to implement two overloaded setters handling both of above mentioned cases. But I have no idea how to handle that in Kotlin. I have tried this:
class SomeExampleClass(var _date: LocalDate) {
var date = _date
set(value) {
when(value) {
is LocalDate -> value
is String -> LocalDate.parse(value)
}
}
}
It doesn't compile. How can I resolve such a problem?

After some time I returned to the problem of overloaded setters and developed the following solution:
class A(_date: LocalDate) {
var date: Any = _date
set(value) {
field = helperSet(value)
}
get() = field as LocalDate
private fun <T> helperSet(t: T) = when (t) {
is LocalDate -> t
is String -> LocalDate.parse(t)
else -> throw IllegalArgumentException()
}
}

So if you just want to construct it (via constructor), just create a secondary constructor
SomeExampleClass(LocalDate.MAX)
SomeExampleClass("2007-12-03")
class SomeExampleClass(var _date: LocalDate) {
constructor(_date: String) : this(LocalDate.parse(_date))
}

Related

Kotlin Data Class Property: Multiple types

In Kotlin, can a property of a data class have multiple types? For example:
val CurrentValue: Double?|String
or
val CurrentValue: String|Array?
I cannot find it in the documentation.
Union types are not a thing in Kotlin.
You may use a sealed class instead.
sealed class CurrentValue<T>(val value: T) {
class TextualValue(value: String) : CurrentValue<String>(value)
class NumericValue(value: Double) : CurrentValue<Double>(value)
}
Which then you can use exhaustive when expressions (similar to switch in other languages) in order to access the value in a type-safe manner:
fun doSomething(value: CurrentValue<*>) {
when(value) {
is TextualValue -> value.value // is recognised as a String
is NumericValue -> value.value // is recognised as a Double
}
}
If creating a type is way too much for you then you can perform a when statement and treat a parameter based on it's type and perhaps normalize it:
fun parseValue(value: Any?): Double? = when(value){
is Double -> value
is String -> value.toDoubleOrNull()
is Int -> value.toDouble()
else -> null
}

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 - when expression over class type

I'm attempting to write an invocation handler that uses a map (supplied at runtime) to implement an interface's getters.
This very crudely works. I know the basic types that may be returned, so I'm OK with having a when expression.
I haven't found a way to avoid using the name of the class as the subject of the when expression; is there a better way?
class DynamicInvocationHandler<T>(private val delegate: Map<String, Any>, clzz: Class<T>) : InvocationHandler {
val introspector = Introspector.getBeanInfo(clzz)
val getters = introspector.propertyDescriptors.map { it.readMethod }
override fun invoke(proxy: Any, method: Method, args: Array<Any>?): Any? {
if (method in getters) {
// get the value from the map
val representation = delegate[method.name.substring(3).toLowerCase()]
// TODO need better than name
when (method.returnType.kotlin.simpleName) {
LocalDate::class.simpleName -> {
val result = representation as ArrayList<Int>
return LocalDate.of(result[0], result[1], result[2])
}
// TODO a few other basic types like LocalDateTime
// primitives come as they are
else -> return representation
}
}
return null
}
}
You can use the types instead of the class names in the when statement. After a type is matched, Kotlin smart cast will automatically cast it
Example
val temporal: Any? = LocalDateTime.now()
when (temporal){
is LocalDate -> println("dayOfMonth: ${temporal.dayOfMonth}")
is LocalTime -> println("second: ${temporal.second}")
is LocalDateTime -> println("dayOfMonth: ${temporal.dayOfMonth}, second: ${temporal.second}")
}
when expressions support any type (unlike Java's switch), so you can just use the KClass instance itself:
when (method.returnType.kotlin) {
LocalDate::class -> {
...
}
...
}

Can a field be cast to non null version of itself?

I have a data class
data class MyModel(private val _data: MyData? = null)
And I want to ensure my data is only accessible when it is not null, else throw.
I use the below which is good.
fun getData(): MyData {
return checkNotNull(_data) { "data shouldn't be null" }
}
However, if I follow the guide as per Override getter for Kotlin data class, the below complaints I need to return MyData? instead of MyData
val data = _data
get(): MyData {
return checkNotNull(field) { "data shouldn't be null" }
}
Is it true that field can't be cast to the Non-null version of it when return?
If your goal is to declare a getter for a Any? property that returns a Any, it's not possible. You'll get the following error:
Getter return type must be equal to the type of the property
So attempting to do something like
val test : String?
get() : String = "hi"
Wouldn't work.
However, you could hide the nullable property and expose a non-nullable property which references the nullable value via casting:
private val test : String? = "hi"
val testNotNull : String = test as String
If test referenced null, an exception will be thrown.
For example:
fun main(args: Array<String>) = print(Demo().testNotNull)
class Demo(private var test: String? = "hi") {
val testNotNull : String
. get() = test as String
}
You can test this snippit out at try.kotlin.org
Although this is not safe. You should rethink your design. If you're not interoping with Java, you shouldn't punish yourself with nullable types.
I don’t think you can. What you did with the fun getData() is a valid approach IMO. Or you could just not use a data class and create a normal class, obviously.
What I think it may work is with something like this:
typealias notNullType = MyData
data class Test(private val _value: MyData? = null) {
val v: notNullType = _value as notNullType
get() { return field }
}
This would totally allow you to do:
fun play() {
val t = Test(null)
print(t.v) //see what I did? :-)
}
THAT BEING SAID… I don’t think “hiding” the ? optional is necessarily a good idea.
It doesn't necessarily mean that the MyData class is null if you cast it like MyData?
The '?' Just allows the object to be null in the instance that it actually becomes null to avoid an exception at runtime.
You can make your class nullable and it can still contain your data.

Union types / extension interfaces

I have several data class with fields, which are used in forms and need them to have a method return true if any of the fields has been filled.
I don't want to rewrite this for all the classes, so I'm doing it like this at the moment:
data class Order(var consumer: String, var pdfs: List<URI>): Form {
override val isEmpty(): Boolean
get() = checkEmpty(consumer, pdfs)
}
data class SomethingElse(var str: String, var set: Set<String>): Form {
override val isEmpty(): Boolean
get() = checkEmpty(str, set)
}
interface Form {
val isEmpty: Boolean
fun <T> checkEmpty(vararg fields: T): Boolean {
for (f in fields) {
when (f) {
is Collection<*> -> if (!f.isEmpty()) return false
is CharSequence -> if (!f.isBlank()) return false
}
}
return true;
}
}
This is obviously not very pretty nor type-safe.
What's a more idiomatic way of doing this, without abstracting every property into some kind of Field-type?
Clarification: What I'm looking for is a way to get exhaustive when, for example by providing all the allowed types (String, Int, List, Set) and a function for each to tell if they're empty. Like an "extension-interface" with a method isEmptyFormField.
It's kinda hacky but should work.
Every data class creates set of method per each constructor parameters. They're called componentN() (where N is number starting from 1 indicating constructor parameter).
You can put such methods in your interface and make data class implicitly implement them. See example below:
data class Order(var consumer: String, var pdfs: List) : Form
data class SomethingElse(var str: String, var set: Set) : Form
interface Form {
val isEmpty: Boolean
get() = checkEmpty(component1(), component2())
fun checkEmpty(vararg fields: T): Boolean {
for (f in fields) {
when (f) {
is Collection -> if (!f.isEmpty()) return false
is CharSequence -> if (!f.isBlank()) return false
}
}
return true;
}
fun component1(): Any? = null
fun component2(): Any? = null
}
You can also add fun component3(): Any? = null etc... to handle cases with more that 2 fields in data class (e.g. NullObject pattern or handling nulls directly in your checkEmpty() method.
As I said, it's kinda hacky but maybe will work for you.
If all you are doing is checking for isEmpty/isBlank/isZero/etc. then you probably don't need a generic checkEmpty function, etc.:
data class Order(var consumer: String, var pdfs: List<URI>) : Form {
override val isEmpty: Boolean
get() = consumer.isEmpty() && pdfs.isEmpty()
}
data class SomethingElse(var str: String, var set: Set<String>) : Form {
override val isEmpty: Boolean
get() = str.isEmpty() && set.isEmpty()
}
interface Form {
val isEmpty: Boolean
}
However, if you are actually do something a bit more complex then based on your added clarification I believe that "abstracting every property into some kind of Field-type" is exactly what you want just don't make the Field instances part of each data class but instead create a list of them when needed:
data class Order(var consumer: String, var pdfs: List<URI>) : Form {
override val fields: List<Field<*>>
get() = listOf(consumer.toField(), pdfs.toField())
}
data class SomethingElse(var str: String, var set: Set<String>) : Form {
override val fields: List<Field<*>>
get() = listOf(str.toField(), set.toField())
}
interface Form {
val isEmpty: Boolean
get() = fields.all(Field<*>::isEmpty)
val fields: List<Field<*>>
}
fun String.toField(): Field<String> = StringField(this)
fun <C : Collection<*>> C.toField(): Field<C> = CollectionField(this)
interface Field<out T> {
val value: T
val isEmpty: Boolean
}
data class StringField(override val value: String) : Field<String> {
override val isEmpty: Boolean
get() = value.isEmpty()
}
data class CollectionField<out C : Collection<*>>(override val value: C) : Field<C> {
override val isEmpty: Boolean
get() = value.isEmpty()
}
This gives you type-safety without changing your data class components, etc. and allows you to "get exhaustive when".
You can use null to mean "unspecified":
data class Order(var consumer: String?, var pdfs: List<URI>?) : Form {
override val isEmpty: Boolean
get() = checkEmpty(consumer, pdfs)
}
data class SomethingElse(var str: String?, var set: Set<String>?) : Form {
override val isEmpty: Boolean
get() = checkEmpty(str, set)
}
interface Form {
val isEmpty: Boolean
fun <T> checkEmpty(vararg fields: T): Boolean = fields.all { field -> field == null }
}
The idea here is the same as that of an Optional<T> in Java but without the extra object, etc.
You now have to worry about null safety but if your fields are meant to have a concept of absent/empty then this seems appropriate (UsingAndAvoidingNullExplained · google/guava Wiki).