Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining/merging data classes in Kotlin

Is there a way to merge kotlin data classes without specifying all the properties?

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

with a function with the following signature:

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

where this function checks each property on both classes and where they are different uses the left parameter to create a new MyDataClass.

Is this possible possible using kotlin-reflect, or some other means?

EDIT: more clarity

Here is a better description of what i want to be able to do

  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"))
}
like image 201
Ben Flowers Avatar asked Dec 08 '22 18:12

Ben Flowers


2 Answers

If you want to copy values from the right when values in the left are null then you can do the following:

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

Usage:

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)
like image 140
mfulton26 Avatar answered Dec 10 '22 07:12

mfulton26


A class-specific way to combine data classes when we can define the fields we want to combine would be:

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)
    }
}
like image 45
urgentx Avatar answered Dec 10 '22 07:12

urgentx