简体   繁体   中英

Kotlin data class: how to read the value of property if I don't know its name at compile time?

如果属性名称仅在运行时已知,如何读取 Kotlin 数据类实例中的属性值?

Here is a function to read a property from an instance of a class given the property name ( throws exception if property not found, but you can change that behaviour ):

import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties

@Suppress("UNCHECKED_CAST")
fun <R> readInstanceProperty(instance: Any, propertyName: String): R {
    val property = instance::class.members
                     // don't cast here to <Any, R>, it would succeed silently 
                     .first { it.name == propertyName } as KProperty1<Any, *> 
    // force a invalid cast exception if incorrect type here
    return property.get(instance) as R  
}

build.gradle.kts

dependencies {
    implementation(kotlin("reflect"))
}

Using

// some data class
data class MyData(val name: String, val age: Int)
val sample = MyData("Fred", 33)

// and reading property "name" from an instance...
val name: String = readInstanceProperty(sample, "name")

// and reading property "age" placing the type on the function call...
val age = readInstanceProperty<Int>(sample, "age")

println(name) // Fred
println(age)  // 33

You can do it through reflection , and it's all the same for data classes and normal ones.

The first option is just to use Java reflection:

val name = obj.javaClass
              .getMethod("getName") // to get property called `name`
              .invoke(obj)

You can even make an extension function:

inline fun <reified T : Any> Any.getThroughReflection(propertyName: String): T? {
    val getterName = "get" + propertyName.capitalize()
    return try {
        javaClass.getMethod(getterName).invoke(this) as? T
    } catch (e: NoSuchMethodException) {
        null
    }
}

It calls the public getter. To get a value of a private property you can modify this code using getDeclaredMethod and setAccessible . This will also work for Java objects with the corresponding getters (but it misses the convention of is and has getters for boolean ).

And usage:

data class Person(val name: String, val employed: Boolean)

val p = Person("Jane", true)
val name = p.getThroughReflection<String>("name")
val employed = p.getThroughReflection<Boolean>("employed")

println("$name - $employed") // Jane - true


The second option involves using kotlin-reflect library which you should add to your project separately, here's its documentation . It will let you get actual Kotlin property value, ignoring Java getters.

You can use javaClass.kotlin to get actual Kotlin class token, and then you get a property from it:

 val name = p.javaClass.kotlin.memberProperties.first { it.name == "name" }.get(p)

This solution will work only for Kotlin classes, not for Java ones, but it is much more reliable if you need to work with Kotlin classes: it doesn't depend on the underlying implementation.

The answers above didn't work for me so I've created an extension function for this:

@Throws(IllegalAccessException::class, ClassCastException::class)
inline fun <reified T> Any.getField(fieldName: String): T? {
    this::class.memberProperties.forEach { kCallable ->
        if (fieldName == kCallable.name) {
            return kCallable.getter.call(this) as T?
        }
    }
    return null
}

This is an example call:

val valueNeeded: String? = yourObject.getField<String>("exampleFieldName")

Also include this in your app's build.gradle:

implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

I wonder if it is possible to define the type of the field programmatically. You can get the type easily with:

kCallable.returnType

However you still have to assign the generic type explicitely:

getField<String>

instead of

getField<kCallable.returnType>

Edit: I ended up using the following:

when (prop.call(object)) {
    is ObservableList<*> -> {}
    is Property<*> -> {}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM