Hi all, I have a question about extension function...
# announcements
j
Hi all, I have a question about extension functions, I'm kind of stuck. Hope anyone can point me in the right direction. The problem I'm trying to solve: I want to be able to check when a property in a data class has been set. The goal is to be able to differentiate between a null value (the property has been set to null) and a non present (value was not set). A potential use case is to implement a HTTP Patch where you receive Input Objects as DTO and you want to save in the database entities only the properties set in the DTO. My initial approach: I thought it could be solve with a delegate using a map, so I could use the map to check if the value was actually set. Here is some code, but it is not working.
Copy code
class InputExample(val map: Map<String, Any?>) {
        val name: String? by map
        val age: Int?     by map
        val lastname: String? by map

        fun KProperty<Any>.isPresent(): Boolean {
            //return map.containsKey(this.name)
            return true
        }
 }
I was trying to get to a interface like this:
Copy code
inputType.lastname.isPresent()
e
::lastName.isPresent()
could work, but ... did you mean to write
val name: String? by map
or
val name: String by map.withDefault { "?" }
instead of something that will throw up on non-present value?
j
Yes, sorry. It will be something like
Copy code
val name: String? by map
(I just updated the code) I tried to use it like this:
Copy code
assert(!inputType::lastname.isPresent())
but I got a "Unresolved reference: isPresent"
BTW, any other implementation idea is welcome. I'm pretty new to Kotlin 😅
n
not familiar with reflection based approaches
But offhand seems like the way to go is basically an optional like class
A generic class that stores a T? and a bool
Because nulls coalesce and T?? is the same type as T? sometimes you have to live with this in Kotlin
j
I'm not sure if I'm following the solution.I'm sorry. I may be missing some fundamentals.
are you referring to Java Optionals?
r
the problem why it doesn't work is that in a function like defined like this, there have to be TWO
this
- one of type
InputExample
, and one of type
KProperty<Any>
so if you wanted to call your function, you would have to do smth like this:
Copy code
val test = InputExample(mapOf(
    "name" to null,
    "age" to 4,
    "lastname" to "Lastname",
))

with(test) {
    ::name.isPresent()   // true
}
(after you edit the isPresent to be callable on KProperty<Any?>, because KProperty<String?> is not subtype of non nullable KProperty<Any>)
if you did just
Copy code
test::name.isPresent()
then
this
is only one -
test::name
which is
KProperty<String?>
but the
InputExample
this is missing, that's why you can't call the function
👍 1
t
The java.util.Optional approach (
val name : Optional<String?>
) doesn't work because Optional cannot contain "null". So your are left with
Optional<String>?
which seems rather weird. Instead we can create our own Option class that does support this use case:
Copy code
sealed interface Maybe<out T>
object Missing : Maybe<Nothing> {
    override fun toString(): String = "Missing"
}
data class Present<T>(val value : T) : Maybe<T>
Arrow kt probably has an implementation of this somewhere. So now the example is:
Copy code
data class Example(
    val name : Maybe<String?>,
    val age : Maybe<Int?>,
    val lastName : Maybe<String?>
)

val foo = Example(Missing, Present(null), Present("bar"))
and
Copy code
inputType.lastname is Present
With a few helper methods (
Maybe<T>.get() : T
,
Maybe<T>.getOrNull() : T?
, etc.) it becomes manageable. This is the solution I picked for the scenario of PATCH requests where missing and null are different. I also have a module for Jackson to get the JSON correct (i.e. absent is not written, null is written as null). If you want I can share that with you, but it is quite hairy
y
Also also, as an alternative approach, you can make this much less complicated by simply having a custom setter for each field that sets a boolean value like this:
Copy code
class InputExample(name: String?, age: Int?, lastname: String?) {
    val nameIsPresent: Boolean = false
    val name: String? get() = field 
        set(value) { 
            nameIsPresent = true
            field = value
        }
    ...
}
n
@Timmy I'm not suggesting java.util.Optional
you'd write your own optional
Copy code
class Optional<T>(val isValid: Boolean, val t: T?)
basically
yeah, like what you wrote after
okay, I missed Juan mentioning Java's optional, I understand now 🙂
I don't agree that Youssef's approach is much less complicated, you'll be repeating the exact same code for each field, makes more sense to factor this into a small class
j
Thanks for the help. I'm going to explore the options you've shared. @Timmy I would love to see the library you mentioned if you don't mind. Thanks for the offer!
t
Here you go. It is part of a bigger library, which I can't share, but that is the relevant part
👍 1
j
Thanks!