如何在 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
最佳答案
贴出的代码有几个问题,
this
上合并属性的值时与 other
被调用,所以变成了无限递归。 get
由于方差 这是固定版本。对于生产,我会添加一些检查和错误消息,但这应该适用于“快乐路径”,并希望为您提供基础:
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/