lundi 4 mai 2020

How to merge two objects of a class with nullable fields, keeping the non-null values?

Given a class with a bunch of members, I'd like to merge two instances of it. The resulting instance should keep the non-null values of each of the two inputs. If two non-null values contradict, an exception should be raised.

My current implementation somewhat works, but it does not scale well:

import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.test.fail

class Thing(
    val a: Int,
    var b: String,
    val c: Int? = null,
    val d: Boolean? = null,
    val e: Long? = null,
    val f: String? = null,
    val g: String? = null
)

private fun <T> mergeThingProperty(property: KProperty1<Thing, *>, a: Thing, b: Thing): T {
    val propA = property.get(a)
    val propB = property.get(b)
    val mergedValue = if (propA != null && propB == null) {
        propA
    } else if (propA == null && propB != null) {
        propB
    } else if (propA != null && propB != null) {
        if (propA != propB) {
            throw RuntimeException("Can not merge Thing data on property ${property.name}: $propA vs. $propB.")
        } else {
            propA
        }
    } else {
        null
    }
    @Suppress("UNCHECKED_CAST")
    return mergedValue as T
}

fun mergeTwoThings(thing1: Thing, thing2: Thing): Thing {
    val properties = Thing::class.memberProperties.associateBy { it.name }
    val propertyMissingMsg = "Missing value in Thing properties"
    return Thing(
        mergeThingProperty(properties["a"] ?: error(propertyMissingMsg), thing1, thing2),
        mergeThingProperty(properties["b"] ?: error(propertyMissingMsg), thing1, thing2),
        mergeThingProperty(properties["c"] ?: error(propertyMissingMsg), thing1, thing2),
        mergeThingProperty(properties["d"] ?: error(propertyMissingMsg), thing1, thing2),
        mergeThingProperty(properties["e"] ?: error(propertyMissingMsg), thing1, thing2),
        mergeThingProperty(properties["f"] ?: error(propertyMissingMsg), thing1, thing2),
        mergeThingProperty(properties["g"] ?: error(propertyMissingMsg), thing1, thing2)
    )
}

fun main() {
    val result1 = mergeTwoThings(Thing(a = 42, b = "foo"), Thing(a = 42, b = "foo", c = 23))
    assert(result1.c == 23)
    assert(result1.d == null)

    try {
        mergeTwoThings(Thing(a = 42, b = "foo"), Thing(a = 42, b = "bar"))
        fail("An exception should have been thrown.")
    } catch (ex: RuntimeException) {
    }
}

How can I avoid the manual repetition of each member (currently in mergeTwoThings)?

Also, it would be nice if I would not need an unchecked cast (currently in mergeThingProperty).





Aucun commentaire:

Enregistrer un commentaire