在 Kotlin 中组合/合并数据类

Ben*_*ers 4 kotlin kotlin-reflect

有没有办法在不指定所有属性的情况下合并 kotlin 数据类?

data class MyDataClass(val prop1: String, val prop2: Int, ...//many props)
Run Code Online (Sandbox Code Playgroud)

具有以下签名的函数:

fun merge(left: MyDataClass, right: MyDataClass): MyDataClass
Run Code Online (Sandbox Code Playgroud)

这个函数检查两个类的每个属性,它们不同的地方使用 left 参数来创建一个新的 MyDataClass。

使用 kotlin-reflect 或其他方法可以实现吗?

编辑:更清晰

这是我想要做的更好的描述

  data class Bob(
        val name: String?,
        val age: Int?,
        val remoteId: String?,
        val id: String)

@Test
fun bob(){

    val original = Bob(id = "local_id", name = null, age = null, remoteId = null)
    val withName = original.copy(name = "Ben")
    val withAge = original.copy(age = 1)
    val withRemoteId = original.copy(remoteId = "remote_id")

    //TODO: merge without accessing all properties
    // val result = 
    assertThat(result).isEqualTo(Bob(id = "local_id", name = "Ben", age=1, remoteId = "remote_id"))
}
Run Code Online (Sandbox Code Playgroud)

mfu*_*n26 10

如果要在左侧的值时从右侧复制值,null则可以执行以下操作:

inline infix fun <reified T : Any> T.merge(other: T): T {
    val propertiesByName = T::class.declaredMemberProperties.associateBy { it.name }
    val primaryConstructor = T::class.primaryConstructor
        ?: throw IllegalArgumentException("merge type must have a primary constructor")
    val args = primaryConstructor.parameters.associateWith { parameter ->
        val property = propertiesByName[parameter.name]
            ?: throw IllegalStateException("no declared member property found with name '${parameter.name}'")
        (property.get(this) ?: property.get(other))
    }
    return primaryConstructor.callBy(args)
}
Run Code Online (Sandbox Code Playgroud)

用法:

data class MyDataClass(val prop1: String?, val prop2: Int?)
val a = MyDataClass(null, 1)
val b = MyDataClass("b", 2)
val c = a merge b // MyDataClass(prop1=b, prop2=1)
Run Code Online (Sandbox Code Playgroud)

  • 也许我应该正确评论。它不起作用(NullPointerException) - 至少在我启用了 r8 的情况下。删除可为空变量的强制解包并使返回类型为可为空后。它正在工作。 (3认同)
  • 如果主构造函数包含要合并的字段,这也可以用于非数据类。 (2认同)

hol*_*ava -2

infix fun <T : Any> T.merge(mapping: KProperty1<T, *>.() -> Any?): T {
    //data class always has primary constructor ---v
    val constructor = this::class.primaryConstructor!!
    //calculate the property order
    val order = constructor.parameters.mapIndexed { index, it -> it.name to index }
                                      .associate { it };

    // merge properties
    @Suppress("UNCHECKED_CAST")
    val merged = (this::class as KClass<T>).declaredMemberProperties
                                           .sortedWith(compareBy{ order[it.name]})
                                           .map { it.mapping() }
                                           .toTypedArray()


    return constructor.call(*merged);
}
Run Code Online (Sandbox Code Playgroud)

编辑

infix fun <T : Any> T.merge(right: T): T {
    val left = this;
    return left merge mapping@ {
        //    v--- implement your own merge strategy
        return@mapping this.get(left) ?: this.get(right);
    };
}
Run Code Online (Sandbox Code Playgroud)

例子

val original = Bob(id = "local_id", name = null, age = null, remoteId = null)
val withName = original.copy(name = "Ben")
val withAge = original.copy(age = 1)
val withRemoteId = original.copy(remoteId = "remote_id")

val result = withName merge withAge merge withRemoteId;
Run Code Online (Sandbox Code Playgroud)