I was trying to convert a Map<String, Any> to data class instance:
data class Person(
val name: String,
val age: Int,
val weight: Double?,
)
fun test() {
val map = mapOf(
"name" to "steven",
"age" to 30,
"weight" to 60,
)
val ctor = Person::class.constructors.first();
val params = ctor.parameters.associateWith {
map[it.name]
}
val instance = ctor.callBy(params)
println(instance)
}
The code above throws java.lang.IllegalArgumentException: argument type mismatch because 60 is passed to weight as an Int, and kotlin does not support implicit conversion.
Then I change the type of weight to non-nullable Double
data class Person(
val name: String,
val age: Int,
val weight: Double, // !
)
and that works, even 60F works too.
My question is:
Why implicit conversion works only when type is non-nullable?
How to do implicit conversion when type is nullable?
Assuming this is Kotlin/JVM...
After digging a bit into how
callByis implemented on the JVM, I found that it eventually callsConstructor.newInstance. The documentation for that says:When you use the non-nullable
Doublein Kotlin as a parameter type, it is translated to the primitive typedoublein the JVM world. However, if you use the nullable typeDouble?, it is translated to the reference type wrapperjava.lang.Double. This is because the primitive typedoublecannot be null. A similar thing happens forIntandInt?.The "method invocation conversions" that the
newInstancedocs mentioned (which I think is referring to the list of conversions allowed in an invocation context) does not include a conversion frominttojava.lang.Double. After all, this Java code does not compile:But it would have compiled if
foohad taken the primitivedouble.As for your second question, I can't think of anything better than just checking each argument and parameter type manually, and performing the conversion explicitly for each case.
If you can change
Person, I'd suggest adding a secondary constructor that takes a non-nullableDouble, and change the calling code to choose an appropriate constructor.Or if you really don't like choosing constructors, change the existing one to take
Number?, and convert it to an appropriate numeric type using thetoXXXmethods before using it.