基于 KClass(反射)将字段名称和值的映射反序列化为数据 class

Deserialize map of field names and values to data class based on KClass (Reflection)

我想从 KClass 的实例和 Map 构造一个对象,我在其中将字段名映射到值。

例如:

val testVal = mapOf("title" to "Foo", "description" to "bar")

data class Article(val title: String, val description)

fun <T> deserialize(clazz: KClass<T>, Map<String,String>) : T?  =  //magic

val article = deserialize(Article::class, testVal) 
// here article should be Article{title: "Foo", description: "Bar"} 

我的问题是如何使用 Kotlin 反射来做到这一点?如何从可用数据构建对象?我认为只要您指出正确的方向或提供想法就足够了。我的意思是肯定有一种方法,因为我认为这就是 json 序列化程序所做的(我只想要一个更简单的版本,功能少得多,在不同的数据格式上)。

如果我可以使用只能存储原始数据类型(int、字符串、布尔值)的数据类,我会很高兴。

我知道我可以使用 Jackson 之类的东西并构建一个 JsonNode 并反序列化它,但这不是我想要的。我想用反射构建我自己的版本。

实际上,并不是所有的序列化库都使用反射。例如,kotlinx.serialization 中的 magic 是基于编译器插件生成的额外字节码。它通常比对反射的运行时调用更快 API 并且我不确定在这种情况下“更简单的版本”是否与“更高性能”相同。

如果您还在发明轮子,这就是反射的方法:

fun <T : Any> deserialize(clazz: KClass<T>, properties: Map<String, *>): T {
    val primaryConstructor = clazz.primaryConstructor!! //all data classes has it
    val parameters = primaryConstructor.parameters
        .associateWith { properties[it.name] }
        //Parameters with nullable types or default values in constructor may be omited in JSON
        .filterNot { (parameter, value) -> value == null && parameter.isOptional }
        .toMap()
    return primaryConstructor.callBy(parameters)
}

如果使用 reified 类型参数,您可以稍微简化 API:

inline fun <reified T : Any> deserialize(properties: Map<String, *>): T {
    val clazz: KClass<T> = T::class
    //other code is the same
}

注意,此代码片段不提供任何类型转换,如果 T 中有任何 non-String 参数(并且 Map<String, String> 被传递,则会抛出运行时异常至 properties).