-
-
Save josdejong/fbb43ae33fcdd922040dac4ffc31aeaf to your computer and use it in GitHub Desktop.
import kotlin.reflect.full.declaredMemberProperties | |
import kotlin.reflect.full.primaryConstructor | |
/** | |
* Merge two data classes | |
* | |
* The resulting data class will contain: | |
* - all fields of `other` which are non null | |
* - the fields of `this` for the fields which are null in `other` | |
* | |
* The function is immutable, the original data classes are not changed | |
* and a new data class instance is returned. | |
* | |
* Example usage: | |
* | |
* val a = MyDataClass(...) | |
* val b = MyDataClass(...) | |
* val c = a merge b | |
*/ | |
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.associateWith { parameter -> | |
val property = nameToProperty[parameter.name]!! | |
(property.get(other) ?: property.get(this)) | |
} | |
return primaryConstructor.callBy(args) | |
} |
It's indeed a shallow merge. Deep merge would be interesting.
@josdejong did you have any solution about. A deep merge would be interesting?
@e-g-hategan I have the same requirement.
i m Doing Single level marge using This Code
inline infix 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.associateWith { parameter ->
val property = nameToProperty[parameter.name]!!
(property.get(other) ?: property.get(this))
}
val mergedObject = primaryConstructor.callBy(args)
nameToProperty.values.forEach { it ->
run {
val property = it as KMutableProperty<*>
val value = property.javaGetter!!.invoke(other) ?: property.javaGetter!!.invoke(this)
property.javaSetter!!.invoke(mergedObject, value)
}
}
return mergedObject
}
How can I add extra condition, for example:
- The resulting data class will contain:
-
- all fields of
other
which are non null or zero
- all fields of
-
- the fields of
this
for the fields which are null or zero inother
- the fields of
or
- The resulting data class will contain:
-
- all fields of
other
which are non null or Double 0.0
- all fields of
-
- the fields of
this
for the fields which are null or Double 0.0 inother
- the fields of
Which line I have to put this kind of conditions?
@talatkuyuk this logic captured in the line:
parameter to (property.get(other) ?: property.get(this))
Right now the behavior is: take the property from other
, but if that is null
, take the property from from this
.
You can adjust the exact behavior of when to pick a property from either other
or this
as you like, i.e. add an extra conditional check to pick other
only when it is non null and not zero (when it is a numeric value).
@josdejong Thank you very much. I understand it, but I am new in Kotlin. I am not familiar with to
operator. I didn't write any inflix function yet. Just I needed this.
- The resulting data class will contain:
-
- all fields of
other
which are non null or non Double with value 0.0
- all fields of
-
- the fields of
this
for the fields which are null or Double with value 0.0 inother
- the fields of
I tried to make it, I am stuck.
Something like this - just change the exact logic to meet your needs (and think through what logic you need for Double, Float, Int. etc, there may be a smart way to check whether any numeric type is zero, I don't know):
infix inline fun <reified T : Any> T.mergeIgnoreZero(other: T): T {
val nameToProperty = T::class.declaredMemberProperties.associateBy { it.name }
val primaryConstructor = T::class.primaryConstructor!!
val args = primaryConstructor.parameters.associateWith { parameter ->
val property = nameToProperty[parameter.name]!!
val valueOther = property.get(other)
val valueThis = property.get(this)
if (valueOther is Double && valueOther == 0.0) {
valueThis
} else {
valueOther ?: valueThis
}
}
return primaryConstructor.callBy(args)
}
@josdejong Thank you very much. It worked.
👍
I was hoping this would work with let's call them deep classes?
data class Some(val a: String?, val b: String?)
data class Other(val some: Some?, val c: String?, val d: String?)
when i do
Other(Some("a", null), "c", null).merge(Other(Some(null, "b"), null, "d))
I'd expect to get
Other(Some("a", "b"), "c", "d")