kotlin

How to get value of field by reflection?


I need to get the value of a property without mentioning the property's name, only by its type. For example, get the value of the field property if it is an IData interface.

interface IData {
    val field: String
}

data class Data(
    override val field: String
): IData

val data = Data("field value")

data::class.declaredMembers.forEach { member ->
    member::class.memberProperties.forEach { property ->
        if (property.returnType.classifier == String::class) {
            val value = // TODO
        }
    }
}

I have tried the next:

value = property.getter.invoke() // No value passed for parameter 'p1'
value = property.getter.invoke(member) // Type mismatch. Required: Nothing Found: KCallable<*>
value = property.invoke() // No value passed for parameter 'p1'
value = property.call(data) as String // Callable expects 1 arguments, but 0 were provided.

instance = member.call(data)
value = property.invoke(instance) // Type mismatch. Required: Nothing Found: Any?
value: String = property.get(instance) as String // Type mismatch. Required: Nothing Found: Any?

How to obtain value from the found property?


Solution

  • There are two problems here.

    First is that you must have misunderstood something in the Reflection API, because you iterate over members of data, then for each member you iterate over its members and look for String. For the above example, you first find the member field: String, then you search the String class for a String member stored inside it. Instead, we should search for members/properties on the first level, not 2 levels deep.

    Second, the reason why we see out Data or Nothing is that the compiler isn't smart enough to recognize we picked members of exactly the same object that we then use to acquire the value. Compiler isn't entirely sure we got members of the same type that we use for acquiring values, so it denies this code as not type-safe. But because we are sure this is the same object, so the operation is safe, we can make an unchecked cast.

    Applying both, we get this code:

    data::class.memberProperties.forEach { property ->
        if (property.returnType.classifier == String::class) {
            @Suppress("UNCHECKED_CAST")
            val value = (property as KProperty1<Any, String>).get(data)
        }
    }