Search code examples
kotlinreflectionkotlin-reflect

Set property by string in Kotlin using reflection?


I have an object:

class User {
  var id: String? = null
  var name: String? = null
}

and list of pairs:

val fieldsToChange = listOf<Pair<String, String>>(Pair("name", "foo"), Pair("id", "bar"))

I would like to iterate trough list of pairs and set appropriate values for given properties using reflection.


Solution

  • Given class instance obj we can extract properties with names using obj::class.memberProperties.

    We can construct a mapping from property name to property:

    val nameToProperty = obj::class.memberProperties.associateBy(KProperty<*>::name)
    

    Then we can iterate over fieldsToChange and retrieve property and set it:

    fieldsToChange.forEach { (propertyName, propertyValue) ->
       nameToProperty[propertyName]
           .takeIf { it is KMutableProperty<*> } // take only "settable" (var) properties
           ?.let { it as KMutableProperty<*> } // cast it to mutable property so we can access setter
           ?.let { it.setter.call(obj, propertyValue) } // call the setter
    }
    

    Additionally, we can make this generic:

    fun setFields(obj: Any, fieldsToChange: List<Pair<String, Any?>>) {
        val nameToProperty = obj::class.memberProperties.associateBy(KProperty<*>::name)
        fieldsToChange.forEach { (propertyName, propertyValue) ->
            nameToProperty[propertyName]
                    .takeIf { it is KMutableProperty<*> }
                    ?.let { it as KMutableProperty<*> }
                    ?.let { it.setter.call(obj, propertyValue) }
        }
    }
    
    val user = User()
    setFields(user, fieldsToChange)
    
    assert(user.name == "foo")
    assert(user.id == "bar")
    

    Possible improvement would be to optimize nameToProperty to contain only MutableProperties already casted to KMutableProperty