I have an unknown data class being passed into a f...
# getting-started
j
I have an unknown data class being passed into a function - which I use reflection on to figure out the arguments and then map to the result of columns in an arbitrary SQL query in a MutableMap. I then want to create an instance of the data class. In Python, it’d be
MyClass(**mutableMapObj)
I’ve tried various options using kclass and callBy, but what I really want to do is destructure the mutableMapObj into the class instantiation call. I.e. my
MyClass.pleaseTakeAMap(mutableMapObj)
is equivalent to
MyClass(param1 = mutableMapObj["param1"], param2 = mutableMapObj["param2"]
Ideally I could do this without have to add to companion object onto each data class.
It also looks like typing could be the problem here, as the map is a MutableMap<String, Any?> and the params for the data class are many possible types. This is beginning to look like an unsolvable problem - for me at least. Which is most frustrating, as all I want to do is create a list of data classes from an arbitrary SQL query - matching columns returned to args of the data class. It doesn’t sound hard.
Stack Overflow came to my aid here. A brief modification of https://stackoverflow.com/a/67452722 allowed me to get there
Copy code
fun <T : Any> mapToObject(map: Map<String, Any?>, klass: KClass<T>) : T {
    //Get default constructor
    val constructor = klass.constructors.elementAt(1)

    //Map constructor parameters to map values
    val args = constructor
        .parameters
        .map { it to map.get(it.name) }
        .toMap()

    //return object from constructor call
    return constructor.callBy(args)
}
That’s a relief!
👍 1
v
Hello, when I had a similar problem to solve, I used something like that:
Copy code
import kotlin.reflect.KClass

fun <T : Any> Map<String, Any>.convertTo(clazz: KClass<T>): T {
    assert(clazz.isData)
    val constructor = clazz.constructors.firstOrNull() ?: throw IllegalArgumentException("no constructor found")

    return constructor.callBy(
        constructor.parameters.associateWith { parameter ->
            this[parameter.name]
        }.filter { (parameter, value) ->
            !parameter.isOptional || value != null
        }
    )
}
j
Ah, thanks for this. Something else for me to study and understand. Not sure why both your solution and the original stack overflow has the
clazz.constructors.first()
and I needed to access the second. The first had additional parameters not in the data class primary constructor.
v
in the general case, I believe, you cannot rely on the ordering of constructors in
KClass::constructors
, it can be 1st, it can be 2nd, collection of constructors can be a
Set
which doesn't imply any ordering
j
Hmm… that might make it challenging. Still, I’ll persist with my code for a bit and see how it works out. This is mainly a learning exercise for me, and a pretty good one at that. Thanks
v
you can try to get the constructor with the biggest number of arguments: something like
clazz.constructors.maxByOrNull { it.parameters.size }
☝️ 1
j
Probably want the one with the fewest number of arguments actually, but that’s. a good shout
👌 1
Compared to my normal languages of JS and Python, Kotlin is deeep.
Also… quick standards question. I see you’ve used
clazz
but in Python we tend to use
klass
- is that Kotlin ‘standard’?
j
I wouldn't say it is a really established standard, but I've mostly seen
clazz
used in both Java and Kotlin. AFAIR I came across
klass
a couple times too, so you can also follow your heart here 🙂
1