Search code examples
kotliniterationin-placedata-class

How to change in place the values of a data class object through an iteration, Kotlin


I am looking for a way, without having to install a new library, to iterate through a Kotlin data class and to change the values according to their type.

The implementation I would like is the following:

The data class I need:

data class Thing(
    var name: String,
    var a: Double,
    var b: Double,
    var c: Double,
    var d: Double,
    ... hundreds of properties of type Double
)

I need to change all values of type Double by a scalar number like in the following pseudo code:

something = Thing("stuff", 0.96, 0.72, 0.55, 81.4)
scalar = 3.75

for (property, value in something.properties_n_values())
{
    if property type is not a String:
        something[property] = value * scalar
}

print(something)
// returns Thing("stuff", 3.6, 2.7, 2.06, 305.25)

The reasons why I choose a data class instead of a Map or any customized Class:

  • data class has already setter and getter implemented.
  • data class is able to contain different type of values, while as far as I know a Map is able to contain an only type.
  • In a future implementation, each data class element will be a line record from a CSV file.

Kotlin version : 231-1.8.20-IJ8109.175


Solution

  • On the JVM, you can use reflection,

    val something = Thing("stuff", 0.96, 0.72, 0.55, 81.4)
    val scalar = 3.75
    
    val properties = something::class.memberProperties.mapNotNull {
        // this unchecked cast is checked by the takeIf on the next line
        (it as? KMutableProperty1<Thing, Double>)
            ?.takeIf { it.returnType == typeOf<Double>() }
    }
    for (property in properties) {
        property.set(something, property.get(something) * scalar)
    }
    

    But if speed is important to you, I'd suggest you store all the Double properties internally in a MutableArray, and expose separate getters/setters/secondary constructors for using them individually.

    If you don't want to install kotlin.reflect.full, you can also do this with Java reflection:

    val properties = something.javaClass.declaredFields.filter {
        !Modifier.isFinal(it.modifiers) && !Modifier.isStatic(it.modifiers) &&
                it.type == Double::class.java
    }
    for (property in properties) {
        property.isAccessible = true
        property.set(something, property.get(something) as Double * scalar)
    }
    

    This will find all non-final non-static fields and set them, so (unlike with Kotlin reflection) this will not set properties that are not backed by a field.