通过反射的Kotlin数据类示例化

y3bcpkx1  于 2022-11-25  发布在  Kotlin
关注(0)|答案(3)|浏览(231)

我正在为连接到MongoDB的应用程序编写一个小的数据访问层,我希望使用反射自动将查询的实体示例化为(域模型)数据类
我看到人们谈论JSON反序列化到Kotlin数据类,我明白为什么这会是一个选项,因为MongoDB以相关的BSON格式返回数据。
因此给定一个任意类型的字段名和值的Map,我如何定义一个函数toDomain,以便它接受这样一个Map和目标类Class<T>作为参数,并作为结果输出一个T?我遇到了麻烦,因为newInstance()方法调用了(不存在的)默认构造函数和我的数据类构造函数参数的arity和类型不同,所以我也不能“硬编码”这些信息。
编辑:我想澄清的是,我不想知道任何我可以简单地插入到我的代码中的库(除非它们是开源的,并且具有可读的实现),相反,我想了解我在这方面的反射的可能性。

bmp9r5qi

bmp9r5qi1#

你可以使用KClass<T>(如果你从其他代码中得到一个Class<T>clazz.kotlin会给你一个KClass)。它包含了你需要的信息,应该是这样的(基于你自己的答案)

fun <T> Document.asInstanceOf(clazz: KClass<T>): T {
    val constructor = clazz.primaryConstructor!!
    val args = constructor.parameters.map { param ->
        this[param.name, param.type.jvmErasure.java]
    }
    constructor.call(*args)
}

您需要将kotlin-reflect添加到依赖项中才能使用它(不是KClass本身,而是它的大部分功能)。

qoefvg9y

qoefvg9y2#

我找到了一个功能性的解决方案。编译时,我在Kotlingradle构建脚本中提供了-parameters标志:

compileKotlin.kotlinOptions.javaParameters = true

则下面的代码段将起作用:

fun <T> Document.asInstanceOf(clazz: Class<T>): T {
    val constructor = clazz.constructors.first()
    val args = mutableListOf<Any>()
    for (param in constructor.parameters) {
        args.add(this[param, param.type])
    }
    @Suppress("UNCHECKED_CAST")
    return constructor.newInstance(*args.toTypedArray()) as T
}

因为运行时知道我的构造函数参数的名字,所以我可以做一个直接的Map。2这不是最终版本,只是一个最小的例子。

hvvq6cgz

hvvq6cgz3#

这适用于数据类,可能也适用于其他类。

// Supply the args in the primary ctor's declaration order.
inline fun <reified T : Any> createThingy(vararg args: Any?): T =
    T::class.primaryConstructor!!.call(* args)

    @Test
    fun testCreateThingy() {
        data class Datum(
            val x: Int,
            val y: String
        )
        assertEquals(Datum(96, "tears"), createThingy<Datum>(96, "tears"))
    }

相关问题