Search code examples
reflectionkotlinkotlin-reflect

How to set val property with Kotlin reflection?


I would like to return a copy of a class with a different value assigned to a val property.

data class Person(val name: String, val age: Int)

fun main() {
    val person = Person("Morné", 25)
    val property = person::class.declaredMemberProperties.first { it.name == "age" }
    person.copyWithValue(property.name, 22)
}

if age was a var then I could do it like follows:

fun main() {
    val person = Person("Morné", 25)
    val property = person::class.declaredMemberProperties.first { it.name == "age" }
    if (property is KMutableProperty<*>)
        property.setter.call(person, 22)
}

Solution

  • If you really want to return just a copy of the object, you can use copy, e.g.:

    fun main() {
      val person = Person("Morné", 25)
      val newPerson = person.copy(age = 22)
    }
    

    Otherwise if you really must edit the age it must not be a val in the first place. Using reflection you could still adapt the value, but if the answers up to here already suffice, they are the way to go...

    For a more dynamic way you can use the following (I would still target the copy-method, as then you wouldn't accidently update the current object):

    val person = Person("Morné", 25)
    val updates = mapOf("lastname" to "N/A", "age" to 22)
    
    val copiedPerson = with(person::class.memberFunctions.first { it.name == "copy" }) {
      callBy(mapOf(instanceParameter!! to person)
          .plus(updates.mapNotNull { (property, newValue) ->
            parameters.firstOrNull { it.name == property }
                ?.let { it to newValue }
          })
      )
    }
    println(copiedPerson)
    

    which prints:

    Person(name=Morné, age=22)
    

    You could also use something like .let { person::class.cast(it } after callBy if you want to continue with the actual type. If you only want this to work with the Person type you could also exchange person with Person and cast it directly to as Person.