kotlin - Kotlin 中的深度合并数据类

标签 kotlin reflection data-class

如何在 Kotlin 中对两个数据类进行递归/深度合并?像这样的东西:

import kotlin.reflect.*
import kotlin.reflect.full.*

data class Address(
  val street: String? = null,
  val zip: String? = null
)

data class User(
  val name: String? = null,
  val age: Int? = null,
  val address: Address? = null
)

inline fun <reified T : Any> T.merge(other: T): T {
  val nameToProperty = T::class.declaredMemberProperties.associateBy { it.name }
  val primaryConstructor = T::class.primaryConstructor!!
  val args = primaryConstructor.parameters.associate { parameter ->
    val property = nameToProperty[parameter.name]!!
    val type = property.returnType.classifier as KClass<*>
    if (type.isData) {
      parameter to this.merge(other) //inline function can't be recursive
    } else {
      parameter to (property.get(other) ?: property.get(this))
    }
  }
  return primaryConstructor.callBy(args)
}

val u1 = User(name = "Tiina", address = Address(street = "Hämeenkatu"))
val u2 = User(age = 23, address = Address(zip = "33100"))

u1.merge(u2)
// expected: User(age = 23, name= "Tiina", address = Address(zip = "33100", street = "Hämeenkatu")

相关:Combining/merging data classes in Kotlin

最佳答案

贴出的代码有几个问题,

  • 不必要的具体化和内联
  • 当检测到类型 isData 而不是在 this 上合并属性的值时与 other被调用,所以变成了无限递归。
  • get由于方差
  • 不能用于 KProperty1
  • 一些有效但可以做得更好的非惯用语

  • 这是固定版本。对于生产,我会添加一些检查和错误消息,但这应该适用于“快乐路径”,并希望为您提供基础:
    import kotlin.reflect.KClass
    import kotlin.reflect.KParameter
    import kotlin.reflect.KProperty1
    import kotlin.reflect.full.declaredMemberProperties
    import kotlin.reflect.full.isSubclassOf
    import kotlin.reflect.full.primaryConstructor
    
    data class Address(
        val street: String? = null,
        val zip: String? = null
    )
    
    data class User(
        val name: String? = null,
        val age: Int? = null,
        val address: Address? = null,
        val map: Map<String, Int>? = null
    )
    
    fun <T> mergeData(property: KProperty1<out T, Any?>, left: T, right: T): Any? {
        val leftValue = property.getter.call(left)
        val rightValue = property.getter.call(right)
        return rightValue?.let {
            if ((property.returnType.classifier as KClass<*>).isSubclassOf(Map::class)) (leftValue as? Map<*, *>)?.plus(it as Map<*, *>)
            else leftValue?.merge(it)
        } ?: rightValue ?: leftValue
    }
    
    fun <T> lastNonNull(property: KProperty1<out T, Any?>, left: T, right: T) =
        property.getter.call(right) ?: property.getter.call(left)
    
    fun <T : Any> T.merge(other: T): T {
        val nameToProperty = this::class.declaredMemberProperties.associateBy { it.name }
        val primaryConstructor = this::class.primaryConstructor!!
        val args: Map<KParameter, Any?> = primaryConstructor.parameters.associateWith { parameter ->
            val property = nameToProperty[parameter.name]!!
            val type = property.returnType.classifier as KClass<*>
            when {
                type.isData || type.isSubclassOf(Map::class) -> mergeData(property, this, other)
                else -> lastNonNull(property, this, other)
            }
        }
        return primaryConstructor.callBy(args)
    }
    
    
    // verification
    
    val u1 = User(name = "Tiina", address = Address(street = "Hämeenkatu"), map = mapOf("a" to 1))
    val u2 = User(age = 23, address = Address(zip = "33100"), map = mapOf("b" to 2))
    
    check(
        u1.merge(u2) == User(
            age = 23,
            name = "Tiina",
            address = Address(zip = "33100", street = "Hämeenkatu"),
            map = mapOf("a" to 1,"b" to 2)
        )
    ) {
        "doesn't work"
    }
    
    println("Works!")
    

    关于kotlin - Kotlin 中的深度合并数据类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59950612/

    相关文章:

    kotlin - 为什么 Lambda 表达式对于 Kotlin 和 Java 类的行为不同?

    java - 带接口(interface)的 KTor Gson DataConversion

    java - 如何在 Jackson JsonNode 中使用点导航

    android - kotlin.TypeCastException : null cannot be cast to non-null type com. midsizemango.databasekotlin.Note

    .net - Powershell反射查找功能

    android - Jetpack compose mutableStateOf list 在更改列表项类中的属性值时不会触发重新组合

    c# - 协变和逆变中的 IsAssignableFrom

    c# - 解析是获取成员类型的唯一方法吗?

    android - 为什么我的数据类给出空值,除非我对其应用默认值?

    firebase - Firestore 的 `documentSnapshot.toObject(className::class.java)` 如何重新分配在主构造函数中设置的 `val` 值?