【问题标题】:How to convert a Kotlin data class object to map?如何将 Kotlin 数据类对象转换为映射?
【发布时间】:2018-04-16 15:22:49
【问题描述】:

是否有任何简单的方法或任何标准库方法可以通过属性名称将 Kotlin 数据类对象转换为其属性的映射/字典?可以避免反射吗?

【问题讨论】:

    标签: object dictionary kotlin


    【解决方案1】:

    我使用的是 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()
    }
    

    【讨论】:

    • 如何使用这些功能?你能显示一些代码吗?
    • 当然,我在答案中添加了一些示例用法
    • 我尝试了您的代码,但在发表评论之前,我得到了 [personAsMap] 和 [mapAsPerson] 的两个空值。这就是为什么我问了一些例子。这段代码对你有用吗?我正在使用 Kotlin 1.4.0
    • 它绝对适合我。你是如何尝试运行它的?您的课程路径中有 gson 吗?如果有帮助,我已经添加了 gson 导入。我也在使用 kotlin 1.4 和 gson 2.8.6,但我认为这不是版本问题。
    • 我不推荐这个解决方案。 GSON 在 Kotlin 中无法正常工作,因为它不知道其 null 与非 null Kotlin 编译时规则。这是一个更好的解决方案,但不处理嵌套属性,只处理原语:stackoverflow.com/a/59316850/4425374
    【解决方案2】:

    这个扩展函数使用反射,但也许它会帮助像我这样的人在未来遇到这个问题:

    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) }
    }
    

    【讨论】:

    • 不错的解决方案,虽然只是一个注释:这看起来不适用于嵌套数据结构。
    • 你有嵌套数据结构的解决方案吗?
    • 未解决的参考“memberProperties”。它现在被称为“成员”
    【解决方案3】:

    我今天有相同的测试用例,最后我使用 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,您将收到此消息:)
    【解决方案4】:

    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)
        }
    }
    

    【讨论】:

    • 这也解释了@SerialName 注释
    • 这似乎只在数据类是平面的并且没有列表或地图属性时才有效。
    【解决方案5】:

    你可以得到最接近的是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&lt;String, Any?&gt;
    • @EpicPandaForce 我知道,但在 Kotlin 中没有非反射的方法可以做到这一点。最接近的替代方法是在地图中使用委托属性。 (即,您将在构造函数中为地图分配值,而不是属性)
    • 是的,当然,我很确定需要反思。
    【解决方案6】:

    Kpropmap 是一个基于反射的库,它试图让 Kotlin 数据类和 Maps 的使用更加容易。它具有以下相关功能:

    • 可以转换地图tofrom 数据类,但请注意,如果您只需要从数据类转换为地图,只需按照@KenFehling 的回答直接使用反射即可。
    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 changesMap 到模型实例的副本:

    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)
    

    免责声明:我是作者。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多