【发布时间】:2018-04-16 15:22:49
【问题描述】:
是否有任何简单的方法或任何标准库方法可以通过属性名称将 Kotlin 数据类对象转换为其属性的映射/字典?可以避免反射吗?
【问题讨论】:
标签: object dictionary kotlin
是否有任何简单的方法或任何标准库方法可以通过属性名称将 Kotlin 数据类对象转换为其属性的映射/字典?可以避免反射吗?
【问题讨论】:
标签: object dictionary kotlin
我使用的是 jackson 方法,但事实证明这在 Android 上的第一次序列化性能很糟糕 (github issue here)。对于较旧的 android 版本,情况要糟糕得多,(see benchmarks here)
但是您可以使用 Gson 更快地做到这一点。此处显示的双向转换:
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
val gson = Gson()
//convert a data class to a map
fun <T> T.serializeToMap(): Map<String, Any> {
return convert()
}
//convert a map to a data class
inline fun <reified T> Map<String, Any>.toDataClass(): T {
return convert()
}
//convert an object of type I to type O
inline fun <I, reified O> I.convert(): O {
val json = gson.toJson(this)
return gson.fromJson(json, object : TypeToken<O>() {}.type)
}
//example usage
data class Person(val name: String, val age: Int)
fun main() {
val person = Person("Tom Hanley", 99)
val map = mapOf(
"name" to "Tom Hanley",
"age" to 99
)
val personAsMap: Map<String, Any> = person.serializeToMap()
val mapAsPerson: Person = map.toDataClass()
}
【讨论】:
这个扩展函数使用反射,但也许它会帮助像我这样的人在未来遇到这个问题:
inline fun <reified T : Any> T.asMap() : Map<String, Any?> {
val props = T::class.memberProperties.associateBy { it.name }
return props.keys.associateWith { props[it]?.get(this) }
}
【讨论】:
我今天有相同的测试用例,最后我使用 Jackson 对象映射器将 Kotlin 数据类转换为 Map。就我而言,运行时性能并不是一个大问题。我还没有检查细节,但我相信它在引擎盖下使用了反射,但在幕后发生的事情是出于担忧。
例如,
val dataclass = DataClass(p1 = 1, p2 = 2)
val dataclassAsMap = objectMapper.convertValue(dataclass, object:
TypeReference<Map<String, Any>>() {})
//expect dataclassAsMap == mapOf("p1" to 1, "p2" to 2)
【讨论】:
No type arguments expected for class TypeReference using jackson 2.10.0
No type arguments expected for class TypeReference 确保您使用的是com.fasterxml.jackson.core.type.TypeReference 类,如果您导入例如org.springframework.asm.TypeReference,您将收到此消息:)
kotlinx.serialization 有一个实验性的Properties 格式,这使得将 Kotlin 类转换为映射非常简单,反之亦然:
@ExperimentalSerializationApi
@kotlinx.serialization.Serializable
data class Category constructor(
val id: Int,
val name: String,
val icon: String,
val numItems: Long
) {
// the map representation of this class
val asMap: Map<String, Any> by lazy { Properties.encodeToMap(this) }
companion object {
// factory to create Category from a map
fun from(map: Map<String, Any>): Category =
Properties.decodeFromMap(map)
}
}
【讨论】:
你可以得到最接近的是delegated properties stored in a map。
示例(来自链接):
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
但是,将它与数据类一起使用可能效果不佳。
【讨论】:
data class变成Map<String, Any?>。
Kpropmap 是一个基于反射的库,它试图让 Kotlin 数据类和 Maps 的使用更加容易。它具有以下相关功能:
data class Foo(val a: Int, val b: Int)
// Data class to Map
val propMap = propMapOf(foo)
// Map to data class
val foo1 = propMap.deserialize<Foo>()
可以通过使用数据类KProperty的类型信息来读取和写入type-safe way中的Map数据。
给定一个数据类和一个 Map,do other neat things 可以像检测没有相应数据类属性的更改值和无关 Map 键。
表示“部分”数据类(有点像lenses)。例如,假设您的后端模型包含一个 Foo,其中包含 3 个必需的不可变属性,表示为 vals。但是,您想提供一个 API 来修补 Foo 实例。由于它是一个补丁,API 使用者只会发送更新的属性。用于此的 REST API 层显然不能直接反序列化为 Foo 数据类,但它可以接受作为 Map 的补丁。使用 kpropmap 验证 Map 是否具有正确的类型,并将 apply the changes 从 Map 到模型实例的副本:
data class Foo(val a: Int, val b: Int, val c: Int)
val f = Foo(1, 2, 3)
val p = propMapOf("b" to 5)
val f1 = p.applyProps(f) // f1 = Foo(1, 5, 3)
免责声明:我是作者。
【讨论】: