https://kotlinlang.org logo
Title
j

Joe

02/03/2019, 6:38 AM
I'm trying to reflectively access a property value of a data class like this:
it::class.memberProperties.filter { prop -> prop.name == propertyName }.first().get(it)
but getting the following compiler error:
Out-projected type 'KProperty1<out DataClass!, Any?>' prohibits the use of 'public abstract fun get(receiver: T): R defined in kotlin.reflect.KProperty1'
using
it::class.java.getMethod("get${propertyName.capitalize()}").invoke(it)
appears to work, but it seems like there should be a way to do this through KClass directly (and avoid having to generate the getter method name)?
m

Marc Knaup

02/03/2019, 7:52 AM
The problem is that the value of
it
is of type
DataClass
or any of its potential subclasses. Due to that
it::class
is not
KClass<DataClass>
(class descriptor for
DataClass
) but
KClass<out DataClass>
(class descriptor for
DataClass
or any of its subclasses).
it::class.memberProperties.filter { prop -> prop.name == propertyName }.first()
thus returns
KProperty1<out DataClass,Any?>
(property of a
DataClass
or any of its subclasses). You cannot call
get()
in that case because the compiler cannot know what class (
DataClass
or one of its subclasses) has declared the property you have found (it could be of a subclass).
get()
requires a value of that subclass to be passed. Because the compiler cannot know,
get(receiver: T)
resolves to
get(receiver: Nothing)
which effectively makes the method uncallable. The way you work around this depends on the context, which is impossible to tell from the code you've shared. One way is to cast
it::class
from
KClass<out DataClass>
to
KClass<DataClass>
which makes
get()
require a receiver of type
DataClass
or any subclass. As long as you use the same value
it
to both,
it::class
and
.get(it)
, and
it
is declared as
it: DataClass
then this should always be safe.
(it::class as KClass<DataClass>)
	.memberProperties
	.first { prop -> prop.name == propertyName }
	.get(it)
You can also cast the resulting
KProperty1
instead:
it::class
	.memberProperties
	.first { prop -> prop.name == propertyName }
	.let { it as KProperty1<DataClass,Any?> }
	.get(it)
The compiler will issue a warning for the cast because it cannot validate it, but that's okay.
If your property is definitely declared in
DataClass
and never in any subclass (which is the case for all
data class
I guess unless there's compiler magic involved), then you can also use do it like this:
DataClass::class
    .memberProperties
    .first { prop -> prop.name == propertyName }
    .get(it)
That way you only get properties which are declared in
DataClass
- not any subclass - and thus you can pass a
DataClass
instance or also a subclass instance to
get()
.
j

Joe

02/04/2019, 4:56 AM
interesting, especially that the compiler can't figure out the data class is final, but thanks!