Combinar / fusionar classs de datos en Kotlin

¿Hay alguna forma de fusionar las classs de datos de kotlin sin especificar todas las properties?

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

con una function con la siguiente firma:

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

donde esta function verifica cada propiedad en ambas classs y donde son diferentes utiliza el parámetro de la izquierda para crear una nueva MyDataClass.

¿Es esto posible usando kotlin-reflect, o algún otro medio?

EDITAR: más claridad

Aquí hay una mejor descripción de lo que quiero ser capaz de hacer

  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>).declanetworkingMemberProperties .sortedWith(compareBy{ order[it.name]}) .map { it.mapping() } .toTypedArray() return constructor.call(*merged); } 

Editar

 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); }; } 

Ejemplo

 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; 

Si desea copyr valores de la derecha cuando los valores de la izquierda son null , puede hacer lo siguiente:

 infix inline fun <reified T : Any> T.merge(other: T): T { val nameToProperty = T::class.declanetworkingMemberProperties.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) } 

Uso:

 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) 

Sus requisitos son exactamente lo mismo que copyr el valor de la left :

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

Tal vez uno de uso no está entendiendo correctamente el otro. Por favor, elabore si esto no es lo que quiere.

Tenga en count que como no se usa la right , puede hacer que sea vararg y "fusionar" tantas como quiera 🙂

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

EDITAR

Las classs en Kotlin no hacen un seguimiento de sus deltas. Piensa en lo que obtienes mientras atraviesas este process. Después del primer cambio que tienes

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

¿Cómo se supone que debes saber que quieres aplicar el cambio según la age pero no el name ? Si solo está actualizando según nulability, @mfulton tiene una buena respuesta. De lo contrario, debe proporcionar la información usted mismo.

  • Compruebe si la class es un valor válido para KParameter
  • Kotlin comtesting si la function requiere parámetro de instancia
  • ¿Cómo puedo crear una instancia de un object usando valores de parameters de constructor pnetworkingeterminados en Kotlin?