Было несколько проблем в опубликованном коде,
- излишнее преобразование и вставка
- при обнаружении типа isData вместо слияния значений свойства сливаются в
this
с other
был вызван, поэтому он стал бесконечной рекурсией. get
не может использоваться в KProperty1 из-за дисперсии - некоторых не-идиоматических c вещей, которые работают, но могут быть лучше
Вот исправленная версия. Для производства я бы добавил несколько проверок и сообщений об ошибках, но это должно работать для «счастливого пути» и, надеюсь, даст вам основу для построения:
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
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
)
fun <T> mergeValues(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 { 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<*>
if (type.isData) mergeValues(property, this, other) else lastNonNull(property, this, other)
}
return primaryConstructor.callBy(args)
}
// verification
val u1 = User(name = "Tiina", address = Address(street = "Hämeenkatu"))
val u2 = User(age = 23, address = Address(zip = "33100"))
check(u1.merge(u2) == User(age = 23, name = "Tiina", address = Address(zip = "33100", street = "Hämeenkatu"))) {
"doesn't work"
}
println("Works!")