基于 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
).
我想从 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
).