Si tengo por ejemplo una entidad:
data class SampleEntity( val a: Int, val b: Int, val c: Int, val d: Int){ fun update(form: SampleEntityUpdateForm): SampleEntity{ ... return this SampleEntity updated with form } }y un formulario para actualizar esta entidad:
data class SampleEntityUpdateForm( val a: Int?, val b: Int?, val c: Int?, val d: Int? )¿Cuál es la mejor manera de actualizar la entidad con este formulario (es decir, implementar SampleEntity.update(...)), dejando las propiedades que son nulas en SampleEntityUpdateForm como estaban en la entidad original, con una placa de caldera mínima.
Así por ejemplo:
val sampleEntityInst = SampleEntity(1,2,3,4) val sampleEntityUpdateForm(b = 20, d = 40) val updatedSampleEntityInst = sampleEntityInst.update(sampleEntityUpdateForm)donde actualizadoSampleEntityInst será igual a SampleEntity(1,20,3,40).
obviamente podría implementar SampleEntity.update como:
fun update(form: SampleEntityUpdateForm) = SampleEntity( a = form.a ?: this.a, b = form.b ?: this.b, c = form.c ?: this.c, d = form.d ?: this.d, )Pero esto tiene un poco de repetición/repetición. ¿Hay algo ya hecho para permitir hacer esto con menos repetición/placa de caldera?
Compruebe el método de copia disponible en todas las clases de datos, por ejemplo:
data class SampleEntity( val a: Int, val b: Int, val c: Int, val d: Int){ fun update(form: SampleEntityUpdateForm): SampleEntity{ return this.copy(a = form.a, b = form.b, ...) } } Editar : me perdí el giro de nulabilidad. El uso de la función magic a continuación funciona, pero en mi opinión, el enfoque directo tiene una mejor ergonomía.
Puede haber casos de uso en los que la reflexión esté bien: una gran cantidad de propiedades, una transformación complicada de los parámetros entrantes, etc.
fun main() { val sampleEntityForm = SampleEntityUpdateForm(a = null, b = 20, c = null, d = 40) val sampleEntityInst = SampleEntity(1, 2, 3, 4) magic(sampleEntityForm, sampleEntityInst).also { println(it) } // prints: SampleEntity(a=1, b=20, c=3, d=40) } fun magic(from: SampleEntityUpdateForm, using: SampleEntity): SampleEntity { val fromValues = from::class.members.filterIsInstance<KProperty1<SampleEntityUpdateForm, Int?>>().map { it.get(from) } val usingValues = using::class.members.filterIsInstance<KProperty1<SampleEntity, Int>>().map { it.get(using) } val nullsEliminated = usingValues.mapIndexed { i, value -> fromValues[i] ?: value }.toTypedArray() return SampleEntity::class.primaryConstructor!!.call(*nullsEliminated) }Este es el problema con la escritura estática. El modelo y la repetitividad vienen incorporados. Creo que tu ejemplo con a = form.a ?: this.a es una buena solución. Al usar un lenguaje escrito estáticamente como Kotlin, es mejor aceptar el modelo estándar y seguir adelante.
Si quieres ser más elegante, tienes que usar algo más dinámico. Por ejemplo, puede utilizar JSON, que es más flexible. Esta solución utiliza JsonMergePatch de JSON-P para crear un parche y aplicarlo a su objeto. Estoy usando Jackson para convertir entre objetos POJO y JSON a través de la reflexión.
Este método tiene la ventaja de poder establecer valores en null a través del parche también. Si el valor en el parche es null , establece el valor de destino en null . Si el valor no está en el parche, se conserva el valor existente. Esto es algo que no puedes hacer muy bien de forma nativa; tienes que empezar a volverte loco con cosas como nullable Optional s.
fun main() { val original = SampleEntity(a = 0, b = 0, c = 0, d = 0) val patch = Json.createObjectBuilder() .add("b", 2) .add("d", 4) .build().let { Json.createMergePatch(it) } val patched: SampleEntity = original.update(patch) println(patched) } data class SampleEntity( val a: Int, val b: Int, val c: Int, val d: Int ) { fun update(patch: JsonMergePatch): SampleEntity { val jsonValue = objectMapper.convertValue<JsonValue>(this) val patched = patch.apply(jsonValue) return objectMapper.convertValue(patched) } companion object { private val objectMapper: ObjectMapper = ObjectMapper() .registerKotlinModule() .registerModule(JSONPModule()) } }Producción:
SampleEntity(a=0, b=2, c=0, d=4)Dependencias:
dependencies { implementation("jakarta.json", "jakarta.json-api", "2.0.1") implementation("org.glassfish", "jakarta.json", "2.0.1") implementation("com.fasterxml.jackson.core", "jackson-core", "2.13.0") implementation("com.fasterxml.jackson.datatype", "jackson-datatype-jakarta-jsonp", "2.12.2") implementation("com.fasterxml.jackson.module", "jackson-module-kotlin", "2.13.0") }