Объединение / объединение классов данных в Котлин

Есть ли способ объединить классы данных kotlin без указания всех свойств?

data class MyDataClass(val prop1: String, val prop2: Int, ...//many props) 

с функцией со следующей сигнатурой:

 fun merge(left: MyDataClass, right: MyDataClass): MyDataClass 

где эта функция проверяет каждое свойство на обоих классах и где они разные, использует левый параметр для создания нового MyDataClass.

Возможно ли это с помощью котлин-отражения или каких-то других средств?

EDIT: больше ясности

Вот лучшее описание того, что я хочу сделать

  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; 

Если вы хотите скопировать значения справа, когда значения в левом null равны null вы можете сделать следующее:

 infix 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]!! parameter to (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) 

Ваши требования точно такие же, как копирование left значения:

 fun merge(left: MyDataClass, right: MyDataClass) = left.copy() 

Возможно, одно из них неправильно понимает другое. Пожалуйста, уточните, если это не то, что вы хотите.

Обратите внимание: поскольку right не используется, вы можете сделать его vararg и «merge» столько, сколько хотите 🙂

 fun merge(left: MyDataClass, vararg right: MyDataClass) = left.copy() val totallyNewData = merge(data1, data2, data3, data4, ...) 

РЕДАКТИРОВАТЬ

Занятия в Котлине не отслеживают их дельт. Подумайте, что вы получите, когда будете проходить этот процесс. После первого изменения у вас есть

 current = Bob("Ben", null, null, "local_id") next = Bob(null, 1, null, "local_id") 

Как это должно знать, что вы хотите, чтобы применить изменение к age но не name ? Если вы просто обновляетесь на основе nullability, @mfulton имеет хороший ответ. В противном случае вам необходимо предоставить информацию самостоятельно.