Kotlin Reflection - Returns an instance with set properties values based on it's property name - kotlin

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?

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

Related

Kotlin get all property value of data class

Is there a syntactic sugar in Kotlin to iterate on each field/property value of a data class?
Sample:
data class User(
var firstName: String = DEFAULT_VALUE_STRING,
var middleName: String = DEFAULT_VALUE_STRING,
var lastName: String = DEFAULT_VALUE_STRING
)
val user = User()
Then check if any of the property's value is empty, considering all of it is String data type with something like this
if (user.properties.any{ it.isBlank() }) {
// TODO ...
}
Probably the closest you'll get is checking all the values of all the generated componentX() functions (since they're only created for the constructor parameter properties, the "data" in a data class) but yeah that involves reflection.
If I were you, I'd create an interface with a properties property and make all your data classes implement that - something like this:
import kotlin.reflect.KProperty0
interface HasStringProperties {
val properties: List<KProperty0<String>>
}
data class User(
var firstName: String = "",
var middleName: String = "",
var lastName: String = ""
) : HasStringProperties {
override val properties = listOf(::firstName, ::middleName, ::lastName)
}
fun main() {
val user = User("Funny", "", "Name")
println(user.properties.any {it.get().isBlank()})
}
So no, it's not automatic - but specifying which properties you want to include is simple, and required if you're going to access it on a particular class, so there's an element of safety there.
Also, because you're explicitly specifying String properties, there's type safety included as well. Your example code is implicitly assuming all properties on your data classes will be Strings (or at least, they're a type with an isBlank() function) which isn't necessarily going to be true. You'd have to write type-checking into your reflection code - if you say "I don't need to, the classes will only have String parameters" then maybe that's true, until it isn't. And then the reflection code has to be written just because you want to add a single age field or whatever.
You don't actually have to use property references in Kotlin either, you could just grab the current values:
interface HasStringProperties {
val properties: List<String>
}
data class User(
var firstName: String = "",
var middleName: String = "",
var lastName: String = ""
) : HasStringProperties {
// getter function creating a new list of current values every time it's accessed
override val properties get() = listOf(firstName, middleName, lastName)
}
fun main() {
val user = User("Funny", "", "Name")
println(user.properties.any {it.isBlank()})
}
It depends whether you want to be able to reference the actual properties on the class itself, or delegate to a getter to fetch the current values.
And of course you could use generics if you want, list all the properties and use filterIsInstance<String> to pull all the strings. And you could put a function in the interface to handle a generic isEmpty check for different types. Put all the "check these properties aren't 'empty'" code in one place, so callers don't need to concern themselves with working that out and what it means for each property

Create instance of class kotlin

How can I create an instance of InfoA that contains also title. Do I need to modify the classes?
Can't specify the title.
Also, do I need to create setters for it? To not access with the _
val info = InfoA(_subtitle = "SUBTITLE", title = ...)
open class Info(
open val action: Action = Action(),
open val title: String? = ""
) {
fun hasAction(): Boolean = action.hasAction()
}
class InfoA(
private val _subtitle: String? = "",
private val _image: String? = "",
private val _backgroundImage: String? = "",
private val _backgroundColor: String? = null,
private val _foregroundColor: String? = null,
private val _borderColor: String? = null
) : Info() {
val subtitle: String
get() = _subtitle.orEmpty()
val image: String
get() = _image.orEmpty()
val backgroundImage: String
get() = _backgroundImage.orEmpty()
val backgroundColor: Int?
get() = if (_backgroundColor != null) convertRgbStringToColorInt(_backgroundColor) else null
val foregroundColor: Int?
get() = if (_foregroundColor != null) convertRgbStringToColorInt(_foregroundColor) else null
val borderColor: Int?
get() = if (_borderColor != null) convertRgbStringToColorInt(_borderColor) else null
}
As the code is written, title is a val, so it can't be changed from its initial value — which is empty string if (as in the case of InfoA) something calls its constructor without specifying another value.
If it were changed to be a var, then it could be changed later, e.g.:
val info = InfoA(_subtitle = "SUBTITLE").apply{ title = "..." }
Alternatively, if you want to keep it a val, then InfoA would need to be changed: the most obvious way would be to add a title parameter in its constructor, and pass that up to Info:
class InfoA(
title: String? = "",
// …other fields…
) : Info(title = title) {
Note that this way, InfoA can never use Info's default value for title, so you may need to duplicate that default in InfoA's constructor.
The need to duplicate superclass properties in a subclass constructor is awkward, but there's currently no good way around it.  (See e.g. this question.)  If there are many parameters, you might consider bundling them together into a single data class, which could then be passed easily up to the superclass constructor — but of course users of the class would need to specify that.  (Some people think that having more than a few parameters is a code smell, and that bundling them together can often improve the design.)

Kotlin multiple variables Null checker method implementation

I have multiple variables that can be nullable and i need to check them ( Strings and Dates ) .
I need a method where i pass it X number of variables and it returns me a list of the variables that are null.
I was thinking something that i can call like this :
internal fun checkNullVariables ( var x, var y , ..... ) : MutableList<String>{
// yada yada
return listOfNamesOfNullVariables
}
This definitely requires reflection, since you want parameter names. You need to add reflection as a dependency as explained in the documentation to use the below code.
private fun listNullProperties (vararg props: KProperty0<Any?>) : List<String> {
val list = mutableListOf<String>()
for (prop in props)
if (param.get() == null)
list.add(param.name)
return list
}
Usage:
val nullPropertiesByName = listNullParameters(
::myProperty,
::myOtherProperty,
::myDateProperty
)
println(nullPropertiesByName.joinToString())
If it is just about logging, you could:
fun <T> T?.logNull(name: String) {
when(this) {
null -> //log '$name' was null
else -> //do nothing
}
}
and call it like
var a: String? = null
a.logNull("my a variable") // "'my a variable' was null"
I still recommend the Map-approach. You may want to use properties stored in a map to overcome the use of reflection.
Here is an example using a type with 2 dates and 2 strings, both having a nullable and a non-null variant:
class YourData(internal val backedMap : Map<String, Any?>) {
val beginDate : Date by backedMap
val endDate : Date? by backedMap
val maybeString : String? by backedMap
val string : String by backedMap
constructor(beginDate : Date, string : String, endDate: Date? = null, maybeString : String? = null) : this(mapOf(
"beginDate" to beginDate,
"endDate" to endDate,
"maybeString" to maybeString,
"string" to string
))
}
While it may seem more complicated having that additional constructor in place, it just helps to easily create new objects the way you are most comfortable with.
Now you can either supply that function I placed in the comment or any variant of it. I now use an extension function for YourData instead:
fun YourData.getKeysWithNullValues() = backedMap.filterValues { it == null }.keys
Usage then may look as follows:
YourData(Date(), "test string")
.getKeysWithNullValues()
.forEach(::println)
which for this example would print:
endDate
maybeString

How to use get() with backing fields in kotlin

in the below code I am trying to create a getter method as a backing field.
so that, when the getLastNameLen property is invoked it should return the length of the lastNameset.
please refer to the code below and help me to fix the bug.
how to display output of the backing fields
code:
class Thomas (val nickname: String?, val age : Int?) {
//backing field 1
var lastName : String? = null
set(value) {
if (value?.length == 0) throw IllegalArgumentException("negative values are not allowed")
field = value
println("lastname backing field set: ${field} ")
}
val getLastNameLen
get() = {
this.lastName?.length
}
}
output
lastname backing field set: jr.stephan
lastName is jr.stephan
lastNameLen is () -> kotlin.Int?
This is because you are using the = operator which is setting the getter to be a lambda.
You have two options:
val getLastNameLen
get() {
return this.lastName?.length
}
OR
val getLastNameLen
get() = this.lastName?.length
basically use the brackets right after get() to make a getter function, or if you can do it in one line use an = right after the get() but don't include the {} otherwise it will treat it like its a lambda

Match string to object property

I have a set of policies that I want to match a request. If the policy exists, I want to match the request and see if the value matches.
The policies are List<Policies> -> (key: String, value: String) and the request can contain different keys.
Example:
The policies are a set of rules that the request should match.
class Policy {
val key: String,
val value: String
}
The request is a data class that contains different values (all optional), for example surname, firstName, address, ++++
data class Request (
id: Long = 12,
firstName: String = "test",
surname: String = "test",
address: String = "somewhere"
...// more fields
)
The list of policies can look like this (List):
List<Policy> => [
{
key: "surname",
value: "test"
},
{
key: "firstName",
value: "test"
}
]
I don't know how I can match the policies with the request. The Policy.key is a String and the Request can contain all different variations of properties.
How do I match the List of policies with my data class Request?
For your puroposes you need use reflection (you want to find field by name and get value), or change something in your model.
Solution with reflection can be like following:
data class Policy(
val key: String,
val value: String?
)
data class Request(
val id: Int,
val firstName: String? = null,
val surname: String? = null,
val address: String? = null
)
class PolicyException : Exception()
fun checkPolicies(request: Request, policies: List<Policy>) {
policies.forEach { policy ->
val member = request::class.members.find { member -> member.name == policy.key }
val requestMemberValue = member?.call(request)
if (requestMemberValue != policy.value) throw PolicyException()
}
}
fun main(args: Array<String>) {
println("Hello, reflection!")
checkPolicies(Request(id = 0, firstName = "Johnn"), listOf(Policy("firstName", "John")))
}
Also, I changed your policy model to handle nullable values (and still handling properly "null" as string).
But, with this solution you have to be very careful with changing model names. And remeber to do not obfuscate your models.
Also, quite better soltuion is adding Annotation which keeps policy name as annotation property (then problem with changing field name in app will disappear).