Kotlin - get all properties from primary constructor - kotlin

I have created this extension method which gets all properties from a KClass<T>
Extension Method
#Suppress("UNCHECKED_CAST")
inline fun <reified T : Any> KClass<T>.getProperties(): Iterable<KProperty1<T, *>> {
return members.filter { it is KProperty1<*, *> }.map { it as KProperty1<T, *> }
}
Example Usage
data class Foo(val bar: Int) {
val baz: String = String.EMPTY
var boo: String? = null
}
val properties = Foo::class.getProperties()
Result
val com.demo.Foo.bar: kotlin.Int
val com.demo.Foo.baz: kotlin.String
var com.demo.Foo.boo: kotlin.String?
How would I modify this extension method to only return properties that are declared in the primary constructor?
Expected Result
val com.demo.Foo.bar: kotlin.Int

You can take constructor parameters by getting primaryConstructor and then valueParameters,
and because primary constructor is not required for kotlin class we can do something like this
inline fun <reified T : Any> KClass<T>.getProperties(): Iterable<KParameter> {
return primaryConstructor?.valueParameters ?: emptyList()
}
so if we will ask for properties of Foo class
val properties = Foo::class.getProperties()
properties.forEach { println(it.toString()) }
we will get
parameter #0 bar of fun <init>(kotlin.Int): your.package.Foo
and the result is not a KProperty, but a KParameter which may be more aligned to your use case

val <T : Any> KClass<T>.constructorProperties
get() =
primaryConstructor?.let { ctor ->
declaredMemberProperties.filter { prop ->
ctor.parameters.any { param ->
param.name == prop.name
&&
param.type == prop.returnType
}
}
} ?: emptyList()
fun <T : Any> KClass<T>.getProperties(): Iterable<KProperty1<T, *>> =
constructorProperties
This is a rework of previous answers by szymon_prz and Peter Henry, to produce the list of properties declared in the primary constructor, but not:
other primary constructor parameters that are not properties
other properties that are not primary constructor parameters but have matching names and different types
Unfortunately it will still list properties that are not primary constructor parameters but have the same name and type as one of them.
For example:
// only parameter 'bar' is declared as a property
class Foo(val bar: Int, baz: Int, qux: Int, rod: Int) {
val zzz = baz // no parameter zzz
val qux = "##($qux)##" // property is a String but parameter is an Int
val rod = maxOf(0, rod) // property and parameter are both Int
}
val ctorProps = Foo::class.constructorProperties
ctorProps.forEach { println(it.toString()) }
will produce:
val Foo.bar: kotlin.Int
val Foo.rod: kotlin.Int

inline fun <reified T : Any> KClass<T>.getProperties(): List<KProperty<*>> {
val primaryConstructor = primaryConstructor ?: return emptyList()
// Get the primary constructor of the class ^
return declaredMemberProperties.filter {
// Get the declared properties of the class; i.e. bar, baz, boo
primaryConstructor.parameters.any { p -> it.name == p.name }
// Filter it so there are only class-properties whch are also found in the primary constructor.
}
}
To summarize, this function basically takes all the properties found in a class and filters them so only ones that are also found in the primary-constructor stay.

Related

Kotlin - TypeReference<T> Cannot obtain Class<*> For Type Arguments

I've created a Kotlin equivalent of TypeReference<T> like so:
abstract class TypeReference<T> : Comparable<T> {
val type: Type get() = getGenericType()
val arguments: List<Type> get() = getTypeArguments()
final override fun compareTo(other: T): Int {
return 0
}
private fun getGenericType(): Type {
val superClass = javaClass.genericSuperclass
check(superClass !is Class<*>) {
"TypeReference constructed without actual type information."
}
return (superClass as ParameterizedType).actualTypeArguments[0]
}
private fun getTypeArguments(): List<Type> {
val type = getGenericType()
return if (type is ParameterizedType) {
type.actualTypeArguments.toList()
} else emptyList()
}
}
In order to obtain Class<*> of the generic type and its arguments, I've also created the following extension function (and this is where I believe the problem lies, since this is where the stack trace fails).
fun Type.toClass(): Class<*> = when (this) {
is ParameterizedType -> rawType.toClass()
is Class<*> -> this
else -> Class.forName(typeName)
}
I'm unit testing this like so:
#Test
fun `TypeReference should correctly identify the List of BigDecimal type`() {
// Arrange
val expected = List::class.java
val expectedParameter1 = BigDecimal::class.java
val typeReference = object : TypeReference<List<BigDecimal>>() {}
// Act
val actual = typeReference.type.toClass()
val actualParameter1 = typeReference.arguments[0].toClass()
// Assert
assertEquals(expected, actual)
assertEquals(expectedParameter1, actualParameter1)
}
The problem I think, lies in the extension function else -> Class.forName(typeName) as it throws:
java.lang.ClassNotFoundException: ? extends java.math.BigDecimal
Is there a better way to obtain the Class<*> of a Type, even when they're generic type parameters?
You need to add is WildcardType -> ... branch to your when-expression to handle types like ? extends java.math.BigDecimal (Kotlin equivalent is out java.math.BigDecimal), ?(Kotlin equivalent is *), ? super Integer(Kotlin equivalent is in java.math.Integer):
fun Type.toClass(): Class<*> = when (this) {
is ParameterizedType -> rawType.toClass()
is Class<*> -> this
is WildcardType -> upperBounds.singleOrNull()?.toClass() ?: Any::class.java
else -> Class.forName(typeName)
}
Note that in this implementation single upper bound types will be resolved as its upper bound, but all other wildcard types (including multiple upper bounds types) will be resolved as Class<Object>
https://github.com/pluses/ktypes
val typeReference = object : TypeReference<List<BigDecimal>>() {}
val superType = typeReference::class.createType().findSuperType(TypeReference::class)!!
println(superType.arguments.first())// List<java.math.BigDecimal>
println(superType.arguments.first().type?.arguments?.first())// java.math.BigDecimal

Remove nullability from generic type parameter

Is there a way to remove nullability from generic parameter?
interface Test<A: Any?> {
fun <B> surelyNotNull(): B // must be T for any nullable type T?
}
// usage
val test: Test<String?> = TODO()
test.surelyNotNull() // should return non-nullable String
I'm not really sure what you're trying to achive and how do you want to use this, but how about this?
interface ValidationBuilder<A : Any> {
fun validate(a: A?): A
}
class NotNulLValidator<A : Any> : ValidationBuilder<A> {
override fun validate(a: A?): A {
if (a == null) throw IllegalArgumentException()
return a
}
}
How about specifying a non-nullable generic type in the interface? You would need to mark any null-returning methods accordingly.
interface Test<A : Any> {
fun maybeNull(): A?
fun surelyNotNull(): A
}
val test: Test<String> = TODO()
val notNullable: String = test.surelyNotNull()
val nullable: String? = test.maybeNull()
You can express it as an extension method:
fun <A : Any> ValidatorBuilder<A?>.surelyNotNull(): ValidatorBuilder<A> = TODO()
Though in this case you can't implement it for some ValidatorBuilder differently from another.

Kotlin How to create dynamic Object

In javascript we can do something like this
function putritanjungsari(data){
console.log(data.name)
}
let data = {
name:"putri",
div:"m4th"
}
putritanjungsari(data)
In kotlin, i'am creating a function that accept an object as parameter then read it's properties later, how to do that in kotlin that targeting JVM?
If I understood your question correct, you are trying to have a variable that associates keys with some value or undefined(null in kt) if none are found. You are searching for a Map
If you don't know what types you want, you can make a map of type Any? So
Map<String, Any?>
Which is also nullable
Map<String, Any>
If you don't want nullables
Your code for example:
fun putritanjungsari(data: Map<String, Any?>){
print(data["name"])
}
val data: Map<String, Any?> =mapOf(
"name" to "putri",
"div" to "m4th"
)
putritanjungsari(data)
Note that you can't add new keys or edit any data here, the default map is immutable. There is MutableMap (which is implemented the same, only it has a method to put new data)
You can apply the property design pattern to solve your problem.
Here is its implementation in Kotlin:
interface DynamicProperty<T> {
fun cast(value: Any?): T
fun default(): T
companion object {
inline fun <reified T> fromDefaultSupplier(crossinline default: () -> T) =
object : DynamicProperty<T> {
override fun cast(value: Any?): T = value as T
override fun default(): T = default()
}
inline operator fun <reified T> invoke(default: T) = fromDefaultSupplier { default }
inline fun <reified T> required() = fromDefaultSupplier<T> {
throw IllegalStateException("DynamicProperty isn't initialized")
}
inline fun <reified T> nullable() = DynamicProperty<T?>(null)
}
}
operator fun <T> DynamicProperty<T>.invoke(value: T) = DynamicPropertyValue(this, value)
data class DynamicPropertyValue<T>(val property: DynamicProperty<T>, val value: T)
class DynamicObject(vararg properties: DynamicPropertyValue<*>) {
private val properties = HashMap<DynamicProperty<*>, Any?>().apply {
properties.forEach { put(it.property, it.value) }
}
operator fun <T> get(property: DynamicProperty<T>) =
if (properties.containsKey(property)) property.cast(properties[property])
else property.default()
operator fun <T> set(property: DynamicProperty<T>, value: T) = properties.put(property, value)
operator fun <T> DynamicProperty<T>.minus(value: T) = set(this, value)
}
fun dynamicObj(init: DynamicObject.() -> Unit) = DynamicObject().apply(init)
You can define your properties these ways:
val NAME = DynamicProperty.required<String>() // throws exceptions on usage before initialization
val DIV = DynamicProperty.nullable<String>() // has nullable type String?
val IS_ENABLED = DynamicProperty(true) // true by default
Now you can use them:
fun printObjName(obj: DynamicObject) {
println(obj[NAME])
}
val data = dynamicObj {
NAME - "putri"
DIV - "m4th"
}
printObjName(data)
// throws exception because name isn't initialized
printObjName(DynamicObject(DIV("m4th"), IS_ENABLED(false)))
Reasons to use DynamicObject instead of Map<String, Any?>:
Type-safety (NAME - 3 and NAME(true) will not compile)
No casting is required on properties usage
You can define what the program should do when a property isn't initialized
Kotlin is statically typed language, so it required a param type to be precisely defined or unambiguously inferred (Groovy, for instance, addresses the case by at least two ways). But for JS interoperability Kotlin offers dynamic type.
Meanwhile, in your particular case you can type data structure to kt's Map and do not argue with strict typing.
You have to use Any and after that, you have to cast your object, like this
private fun putritanjungsari(data : Any){
if(data is Mydata){
var data = data as? Mydata
data.name
}
}
Just for the sake of inspiration. In Kotlin, you can create ad hoc objects:
val adHoc = object {
var x = 1
var y = 2
}
println(adHoc.x + adHoc.y)

About type parameter of generic in Kotlin

I'm studying kotlin language and I can't distinguish these examples, especially nullable types in a 'where' clause of generic.
Would you tell me the difference?
case 1
class Foo<T> where T: Comparable<T>
class Foo<T> where T: Comparable<T?>
class Foo<T> where T: Comparable<T>?
class Foo<T> where T: Comparable<T?>?
case 2
class Foo<T> where T: Comparable<T>? {
// If a class is declared like above, is a type 'T' already nullable?
// Then,
fun bar(value: T?) { // Should I declare a member function like this to accept null or
// do something
}
fun bar(value: T) { // Should I declare like this instead?
}
}
First, to distinguish T : Comparable<T> and T : Comparable<T?>, take a look at the following example. The difference is whether you can compare T with T?.
class Bar1(var bar : Int) : Comparable<Bar1>{
override fun compareTo(other : Bar1) : Int {
return bar - other.bar
}
}
class Bar2(var bar : Int) : Comparable<Bar2?>{
override fun compareTo(other : Bar2?) : Int {
return bar - ( other?.bar ?: 0 )
}
}
fun main(){
println(Bar1(1) > Bar1(2))
val bar2 : Bar2? = Bar2(2)
println(Bar2(1) > bar2)
}
Output:
false
false
The difference is that
val bar1 : Bar1? = Bar1(2)
println(Bar1(1) > bar1)
will not compile. bar1 must be unwrapped
Second, to distinguish class Foo<T> where T: Comparable<T>? and class Foo<T> where T: Comparable<T>?, it has nothing to do with comparable. Take a look at the following simplified example.
class Foo1<T>(val t : T) where T : Int{
override fun toString() : String{
return "$t"
}
}
class Foo2<T>(val t : T) where T : Int?{
override fun toString() : String{
return "$t"
}
}
fun main(){
println(Foo1(5))
val i : Int? = 5
println(Foo2(i))
}
Output:
5
5
The difference is that println(Foo1(i)) will not compile. i must be unwrapped.

Property delegation baked by mutable map

I have a following code:
class Mapped(var data:Map<String,String>){
val firstName:String by data
}
This works fine in case the Mapped is used as follows:
val mapped = Mapped(mapOf("firstName" to "initialFirstName"))
println(mapped.firstName); // prints "initialFirstName"
However since the data property is mutable we can change it's value i.e.:
mapped.data = mapOf("firstName" to "updated");
However the firstName property still holds the "initialFirstName".
Is there a work around to this, known/documented albeit unexpected (to me) behavior?
Until the issues KT-5870, KT-9772 are resolved you can do the following:
operator fun <V, V1 : V> (() -> Map<in String, V>).getValue(thisRef: Any?, property: KProperty<*>): V1 {
val map = this()
return map[property.name] as V1
}
Which can then be used as follows:
class Mapped(var data:Map<String,String>){
val firstName:String by { data }
}
The above does not handle nullability well. Here's an improved version:
operator fun <V, V1 : V> (() -> Map<in String, V>).getValue(thisRef: Any?, property: KProperty<*>): V1 {
val map = this()
val key = property.name
#Suppress("UNCHECKED_CAST")
val value = map[key] as V1
if (property.returnType.isMarkedNullable) {
return value
} else {
if(value != null){
return value
}
if(map.containsKey(key)){
throw KotlinNullPointerException("Property baking map returned null value for key '$key' for non nullable property: $property")
} else {
throw KotlinNullPointerException("Property baking map has no key '$key' for non nullable property $property")
}
}
}