I don’t know what keywords to use here, so DDG isn...
# getting-started
l
I don’t know what keywords to use here, so DDG isn’t helping much. Does Kotlin have a way to reference a property by variable name? Viz, I have a case where I want to have a list of strings, and then I want to do the same operation on every property of an object that corresponds to those strings. In PHP, I’d do:
Copy code
foreach (['a', 'b', 'c'] as $prop) {
  print $obj->$prop . PHP_EOL;
}
But I don’t know what the Kotlin equivalent is.
h
It is possible, the keyword is "reflection".
l
I am also pondering if a delegate would be better here/in addition. The specific use case is that I want to update a DB model based on a value object that came in via IO. The two objects have almost the same property names, by design. (I think there’s 1 or 2 differences.) And I want to update the model from the corresponding property in the incoming value object iff it’s not-null. If the incoming value is null, do nothing.
Hm. I’m used to “if you’re using reflection at runtime, you’re probably doing it wrong”. Is that true in Kotlin as well? 🙂
h
yes.
it seems your case has really only two variants? I'd definitely make static versions then.
(or have the properties which are the same in a common interface, so you can handle them with the same code path)
l
One sec, let me do a little sample code.
Copy code
class StoredPerson (
    val id: UUID,
    var name: String?,
    var age: Int?,
)

class PersonUpdate (
    val name: String?,
    val age: Int?,
)

// Logic I want to simplify:
if (update.name != null) {
    stored.name = update.name
}
if (update.age != null) {
    stored.age = update.age
}
// Repeat for like 40 properties.
That’s a (very) simplified example of what I’m trying to do, in whatever approach involves the least amount of boilerplate typing.
(And I’m putting the logic in an extension function on StoredPerson.)
So, reflection, delegates (and then specify each name once), long manual extension function, or something else? One reason I’m wary about delegates is I am not certain that I will never want to be able to set the values to null in the future. I don’t right now, but I don’t know about forever.
h
Hm. "It depends" is always the answer 🙂 For 40 properties I'd probably just make one 40-line method. https://pl.kotl.in/4plquhncX
e
it's not that hard to do by reflection,
Copy code
class Person(
    var name: String?,
    var age: Int?,
)
class PersonUpdate(
    var name: String?,
    var age: Int?,
)
fun Person.apply(update: PersonUpdate): Person = apply {
    val personProperties = Person::class.memberProperties
        .filterIsInstance<KMutableProperty1<Person, Any?>>()
        .associateBy { it.name }
    for (updateProperty in PersonUpdate::class.memberProperties) {
        val personProperty = personProperties[updateProperty.name] ?: continue
        val value = updateProperty.get(update) ?: continue
        personProperty.set(this, value)
    }
}
but in production I'd definitely either write the 40 lines or codegen them, instead of something "cleverer" like that
l
Hm, it’s actually closer to 65-70. I don’t know if that changes the balance at all, though. 🙂
update.name?.let { name = it }
- OK, that’s much less bad than I was fearing. Not great, but not terrible.
m
interesting by the way, how would you communicate that a field should become null?
1
l
I don’t know. We’re using null in the incoming object to mean “no change”, and an empty string to mean “set to no value”. Our downstream logic will later be merging the stored object with some other data to produce a materialized combination of them all, and presumably we will want to treat “not set” (null) and “set to an empty value” differently when merging with the other data.
For now I just used the
update.name?.let { name = it}
approach and did it manually, which was acceptable, is only one line per field, and avoids messing with reflection.
j
You could set this up in your object using a
PropertyDelegateProvider
whereby you'd store a reference to that property's name in some internal map. If you don't want to use reflection, it's still possible with some work.