0

I'm trying to create a function that returns any data class object setting it's property values with its property names (if all strings) without changing it's default values I have an example on how it is:

Imagine this data class:

data class StudentProfile(

    var fullName: String = "",

    var mobilePhone: String = "",

    var birthDate: String = "",

    var email: String = ""

)

I want to keep this empty default values, but I wanted a generic function that should work for any class and returns (in this case) this:


return StudentProfile( 
                mobilePhone = "mobilePhone",
                fullName = "fullName",
                email = "email",
                birthDate = "birthDate"
            )

Is it possible?

julioribeiro
  • 1,565
  • 2
  • 14
  • 22
  • 1
    are you creating a library of sorts that will do this for any imaginable class? Or do you have a set number of classes where you want to use this because it seems "easier" than to make an individual fun for each? Because it sounds like an XY problem to me, the way to do this would be by using reflection, which is something you usually want to avoid. – AlexT Mar 16 '21 at 17:58
  • I second what @Alex.T said. Could you please give an example on how you imagine it would look like on the usage site? It would help to have a bit more context on what you're trying to achieve on a bigger scale. – Joffrey Mar 16 '21 at 18:48

1 Answers1

1

This does sound like an X-Y problem (I can't imagine how it would be useful), but I thought it'd be fun to solve anyway.

I'm unclear about whether you want to replace default values or not (since you say you don't but your example does), so this example lets you choose.

Explanation: Make sure all of the constructor's parameters are Strings or optional (have defaults). Otherwise, this is impossible because non-String parameter values could not be specified. Then filter the parameters list to include only the ones we are setting to their own name, and associate them to their names to create a Map<KParameter, String> that we can pass to constructor.callBy.

fun <T: Any> produceWithPropertiesByOwnName(type: KClass<T>, overrideDefaults: Boolean): T {
    val constructor = type.primaryConstructor!!
    val parameters = constructor.parameters
    if (!parameters.all { param -> param.type.classifier == String::class || param.isOptional }){
        error("Class $type primary constructor has required non-String parameters.")
    }

    val valuesByParameter = parameters.filter { it.type.classifier == String::class && (!it.isOptional || overrideDefaults) }
        .associateWith(KParameter::name)
    return constructor.callBy(valuesByParameter)
}
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • Probably worth mentioning all the usual caveats about reflection: in production code, if you're not writing plug-ins/frameworks/compiler tools, then it's usually a bad idea, as it's slow, fragile, harder to read and maintain, and risks a variety of runtime errors that can't be checked at compile time.  It may be a familiar tool to reach for if you have background in dynamic/scripting languages, but in a statically-compiled, strongly-typed language like Kotlin there are usually better ways.  (In this case, though, I can't see a better approach, and this is ingenious!) – gidds Mar 16 '21 at 20:24