Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

153
Views
How to update kotlin data class with properties of a different data class

If I have for example an entity:

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

and a form to update this entity:

data class SampleEntityUpdateForm(
        val a: Int?,
        val b: Int?,
        val c: Int?,
        val d: Int?
)

What is the best way to update the entity with this form (i.e. implementing SampleEntity.update(...)), leaving the properties that are null in SampleEntityUpdateForm as they were in the original entity, with minimum boiler plate.

So for example:

val sampleEntityInst = SampleEntity(1,2,3,4)

val sampleEntityUpdateForm(b = 20, d = 40)

val updatedSampleEntityInst = sampleEntityInst.update(sampleEntityUpdateForm)

where updatedSampleEntityInst will equal SampleEntity(1,20,3,40).

obviosly I could implement SampleEntity.update as:

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

But this has quite a bit of boiler plate/repetition. Is there something already made to allow doing this with less boiler plate/repetition?

over 4 years ago · Santiago Trujillo
2 answers
Answer question

0

Checkout the copy method available on all data classes, e.g:


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

Edit: I did miss the nullability twist. Using the magic function below the following works but it in my opinion the straightforward approach has better ergonomics.

There may be use cases where reflection is OK: a high number of properties, a tricky transformation of the incoming parameters, 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)
}


over 4 years ago · Santiago Trujillo Report

0

This is the problem with static typing. Boilerplate and repetitiveness come built-in. I think your example with a = form.a ?: this.a is a good solution. Using a statically typed language like Kotlin, it's better to accept the boilerplate and move on.

If you want to be more fancy, you have to use something more dynamic. For example, you can go via JSON, which is more flexible. This solution uses JSON-P's JsonMergePatch to create a patch and apply it to your object. I'm using Jackson to convert between POJOs and JSON objects via reflection.

This method has the benefit of being able to set values to null via the patch too. If the value in the patch is null, it sets the target value to null. If the value is not in the patch, the existing value is preserved. This is something you can't do nicely natively; you have to start going crazy with things like nullable Optionals.

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

Output:

SampleEntity(a=0, b=2, c=0, d=4)

Dependencies:

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")
}
over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!