Combining/merging Kotlin 中的数据 类
Combining/merging data classes in Kotlin
有没有办法在不指定所有属性的情况下合并 kotlin 数据类?
data class MyDataClass(val prop1: String, val prop2: Int, ...//many props)
具有以下签名的函数:
fun merge(left: MyDataClass, right: MyDataClass): MyDataClass
此函数在 类 上检查每个 属性 并且它们不同的地方使用左侧参数创建一个新的 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"))
}
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);
}
编辑
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);
};
}
示例
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;
您的要求与复制left
值完全相同:
fun merge(left: MyDataClass, right: MyDataClass) = left.copy()
也许其中一种用途没有正确理解另一种用途。如果这不是您想要的,请详细说明。
请注意,由于未使用 right
,您可以将其设为可变参数,并且 "merge" 随心所欲:)
fun merge(left: MyDataClass, vararg right: MyDataClass) = left.copy()
val totallyNewData = merge(data1, data2, data3, data4, ...)
编辑
类 在 Kotlin 中不跟踪它们的增量。想想你在经历这个过程时会得到什么。第一次更改后你有
current = Bob("Ben", null, null, "local_id")
next = Bob(null, 1, null, "local_id")
它怎么知道您希望 next
将更改应用到 age
而不是 name
?如果您只是根据可空性进行更新,
@mfulton 有一个很好的答案。否则您需要自己提供信息。
如果你想在左边的值是 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)
}
用法:
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)
当我们可以定义要合并的字段时,class-specific 合并数据的方法 类 将是:
data class SomeData(val dataA: Int?, val dataB: String?, val dataC: Boolean?) {
fun combine(newData: SomeData): SomeData {
//Let values of new data replace corresponding values of this instance, otherwise fall back on the current values.
return this.copy(dataA = newData.dataA ?: dataA,
dataB = newData.dataB ?: dataB,
dataC = newData.dataC ?: dataC)
}
}
@mfulton26 的解决方案合并了仅属于主构造函数的属性。我已将其扩展为支持所有属性
inline infix 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]!!
parameter to (property.get(other) ?: property.get(this))
}
val mergedObject = primaryConstructor.callBy(args)
nameToProperty.values.forEach { it ->
run {
val property = it as KMutableProperty<*>
val value = property.javaGetter!!.invoke(other) ?: property.javaGetter!!.invoke(this)
property.javaSetter!!.invoke(mergedObject, value)
}
}
return mergedObject
}
有没有办法在不指定所有属性的情况下合并 kotlin 数据类?
data class MyDataClass(val prop1: String, val prop2: Int, ...//many props)
具有以下签名的函数:
fun merge(left: MyDataClass, right: MyDataClass): MyDataClass
此函数在 类 上检查每个 属性 并且它们不同的地方使用左侧参数创建一个新的 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"))
}
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);
}
编辑
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);
};
}
示例
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;
您的要求与复制left
值完全相同:
fun merge(left: MyDataClass, right: MyDataClass) = left.copy()
也许其中一种用途没有正确理解另一种用途。如果这不是您想要的,请详细说明。
请注意,由于未使用 right
,您可以将其设为可变参数,并且 "merge" 随心所欲:)
fun merge(left: MyDataClass, vararg right: MyDataClass) = left.copy()
val totallyNewData = merge(data1, data2, data3, data4, ...)
编辑
类 在 Kotlin 中不跟踪它们的增量。想想你在经历这个过程时会得到什么。第一次更改后你有
current = Bob("Ben", null, null, "local_id")
next = Bob(null, 1, null, "local_id")
它怎么知道您希望 next
将更改应用到 age
而不是 name
?如果您只是根据可空性进行更新,
@mfulton 有一个很好的答案。否则您需要自己提供信息。
如果你想在左边的值是 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)
}
用法:
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)
当我们可以定义要合并的字段时,class-specific 合并数据的方法 类 将是:
data class SomeData(val dataA: Int?, val dataB: String?, val dataC: Boolean?) {
fun combine(newData: SomeData): SomeData {
//Let values of new data replace corresponding values of this instance, otherwise fall back on the current values.
return this.copy(dataA = newData.dataA ?: dataA,
dataB = newData.dataB ?: dataB,
dataC = newData.dataC ?: dataC)
}
}
@mfulton26 的解决方案合并了仅属于主构造函数的属性。我已将其扩展为支持所有属性
inline infix 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]!!
parameter to (property.get(other) ?: property.get(this))
}
val mergedObject = primaryConstructor.callBy(args)
nameToProperty.values.forEach { it ->
run {
val property = it as KMutableProperty<*>
val value = property.javaGetter!!.invoke(other) ?: property.javaGetter!!.invoke(this)
property.javaSetter!!.invoke(mergedObject, value)
}
}
return mergedObject
}