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
Related
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)
}
I need a data class with two different constructors as shown. But how do I do getter & setter for the secondary constructor of data class in Kotlin? I tried multiple changes, not able to figure it out. In the below snippet, I am not getting the right import for get() and set()
data class user(var phone: String) {
constructor(phone: String, name : String) : this(phone) {
var name: String = name
get()= field
set(value) {
field = value
}
}
}
It appears you want two constructors, one which only requires a "phone" argument and another which requires both a "phone" and "name" argument. Overall, your data class will have two properties regardless of which constructor is used: phone and name. You could accomplish this with the following:
data class User(var phone: String) {
var name: String = ""
constructor(phone: String, name: String) : this(phone) {
this.name = name
}
}
However, as this is Kotlin, you should prefer default parameter values over overloaded functions/secondary constructors:
data class User(var phone: String, var name: String = "")
Kotlin has a const-keyword. But I don't think constants in kotlin are what I think they are. It seems to very different to const in C++. It seems to me that its is only available for static members and to what are primitives in Java and do not compile for class-variables:
data class User(val name: String, val id: Int)
fun getUser(): User { return User("Alex", 1) }
fun main(args: Array<String>) {
const val user = getUser() // does not compile
println("name = ${user.name}, id = ${user.id}")
// or
const val (name, id) = getUser() // does not compile either
println("name = $name, id = $id")
}
As this seems not to work, I think what i really want is a second class, that deletes the operations i don't want to support:
class ConstUser : User
{
ConstUser(var name: String, val id: int) : base(name, id)
{ }
/// Somehow delte the setters here?
}
The obvious downside to such a apprach is that I must not forget to change this class, in case i change User, something that looks very dangerous to me.
But I'm not sure how to do this. So the question is: How does one make immutable objects in ideomatic Kotlin?
The const modifier in Kotlin is used for compile-time constants. Immutability is done with a val keyword.
Kotlin has two types of properties: read-only val and mutable var. vals are equivalent to Java's finals (I don't know how this relates to const in C++, though) and properties or variables declared as such can't change their values once set:
data class User(val name: String, val id: Int)
val user = User("Alex", 1)
user.name = "John" // won't compile, `val` cannot be reassigned
user = User("John", 2) // won't compile, `val` cannot be reassigned
You don't have to hide or delete somehow any setters of val properties as such properties don't have setters.
In Kotlin, when creating a custom DSL, what is the best way to force filling required fields inside the builder's extension functions in compile time. E.g.:
person {
name = "John Doe" // this field needs to be set always, or compile error
age = 25
}
One way to force it is to set value in a function parameter instead of the body of the extension function.
person(name = "John Doe") {
age = 25
}
but that makes it a bit more unreadable if there are more required fields.
Is there any other way?
New type inference enables you to make a null-safe compile-time checked builder:
data class Person(val name: String, val age: Int?)
// Create a sealed builder class with all the properties that have default values
sealed class PersonBuilder {
var age: Int? = null // `null` can be a default value if the corresponding property of the data class is nullable
// For each property without default value create an interface with this property
interface Named {
var name: String
}
// Create a single private subclass of the sealed class
// Make this subclass implement all the interfaces corresponding to required properties
private class Impl : PersonBuilder(), Named {
override lateinit var name: String // implement required properties with `lateinit` keyword
}
companion object {
// Create a companion object function that returns new instance of the builder
operator fun invoke(): PersonBuilder = Impl()
}
}
// For each required property create an extension setter
fun PersonBuilder.name(name: String) {
contract {
// In the setter contract specify that after setter invocation the builder can be smart-casted to the corresponding interface type
returns() implies (this#name is PersonBuilder.Named)
}
// To set the property, you need to cast the builder to the type of the interface corresponding to the property
// The cast is safe since the only subclass of `sealed class PersonBuilder` implements all such interfaces
(this as PersonBuilder.Named).name = name
}
// Create an extension build function that can only be called on builders that can be smart-casted to all the interfaces corresponding to required properties
// If you forget to put any of these interface into where-clause compiler won't allow you to use corresponding property in the function body
fun <S> S.build(): Person where S : PersonBuilder, S : PersonBuilder.Named = Person(name, age)
Use case:
val builder = PersonBuilder() // creation of the builder via `invoke` operator looks like constructor call
builder.age = 25
// builder.build() // doesn't compile because of the receiver type mismatch (builder can't be smart-casted to `PersonBuilder.Named`)
builder.name("John Doe")
val john = builder.build() // compiles (builder is smart-casted to `PersonBuilder & PersonBuilder.Named`)
Now you can add a DSL function:
// Caller must call build() on the last line of the lambda
fun person(init: PersonBuilder.() -> Person) = PersonBuilder().init()
DSL use case:
person {
name("John Doe") // will not compile without this line
age = 25
build()
}
Finally, on JetBrains open day 2019 it was said that the Kotlin team researched contracts and tried to implement contracts that will allow creating safe DSL with required fields. Here is a talk recording in Russian. This feature isn't even an experimental one, so
maybe it will never be added to the language.
In case you're developing for Android I wrote a lightweight linter to verify mandatory DSL attributes.
To solve your use case you will only need to add an annotation #DSLMandatory to your name property setter and the linter will catch any place when it is not assigned and display an error:
#set:DSLMandatory
var name: String
You can take a look here:
https://github.com/hananrh/dslint/
Simple, throw an exception if it's not defined in your DLS after the block
fun person(block: (Person) -> Unit): Person {
val p = Person()
block(p)
if (p.name == null) {
// throw some exception
}
return p
}
Or if you want to enforce it at build time, just make it return something useless to the outer block if not defined, like null.
fun person(block: (Person) -> Unit): Person? {
val p = Person()
block(p)
if (p.name == null) {
return null
}
return p
}
I'm guessing your going off this example so maybe address would be the better example case:
fun Person.address(block: Address.() -> Unit) {
// city is required
var tempAddress = Address().apply(block)
if (tempAddress.city == null) {
// throw here
}
}
But what if we wanted to ensure everything was defined, but also wanted to let you do it in any order and break at compile time. Simple, have two types!
data class Person(var name: String = null,
var age: Int = null,
var address: Address = null)
data class PersonBuilder(var name: String? = null,
var age: Int? = null,
var address: Address? = null)
fun person(block: (PersonBuilder) -> Unit): Person {
val pb = PersonBuilder()
block(p)
val p = Person(pb.name, pb.age, pb.address)
return p
}
This way, you get to you the non-strict type to build, but it better be null-less by the end. This was a fun question, thanks.
Why it error when i make userName is public:
Error:(2, 5) Kotlin: Platform declaration clash: The following
declarations have the same JVM signature
(getUserName()Ljava/lang/String;):
fun (): String defined in User
fun getUserName(): String defined in User
Error:(4, 5) Kotlin: Platform declaration clash: The following declarations have the same
JVM signature (getUserName()Ljava/lang/String;):
fun (): String defined in User
fun getUserName(): String defined in User
But i make userName is private is working fine
class User{
/*private*/ var userName: String = "Emily"
fun getUserName(): String{
return userName
}
}
fun main(args: Array<String>){
val User = User()
print(User.getUserName())
}
By making your userName property public, Kotlin will create corresponding getUserName() and setUserName() functions for you. When it does this, writing your own getUserName() is redundant - the same function with the same signature is effectively present twice - and the compiler won't allow it.
If you want the userName field to be a public property (with a generated getter and setter), then you can't also write the getter yourself. This would be adequate:
var userName: String = "Emily"
If you wanted userName to have a public getter and a private setter (which seems like what you intended), this is the Kotlin way to do that:
var userName: String = "Emily"
private set
And finally, you could still create custom accessors on a property (e.g., if you wanted extra logic, such as to return it lowercased). The Kotlin way to do that looks like this:
private var _userName: String = "Emily"
var userName: String
get() = _userName.toLowerCase()
set(value) { _userName = value }
Also, note that the way you access the property is different depending on whether you're accessing it from Kotlin or Java. From Kotlin, you just write user.userName, but in Java, you'd write user.getUserName().
When you define var userName you are really defining a property, not just a field. Along with the property comes an implicit getUserName() and setUserName() method. By adding your own getUserName(), you are shadowing the one Kotlin is creating for you automatically.
You can safely drop your getUserName() and the make your field non-private and it should work fine. The idiomatic way to write your code would be something like this:
class User {
var userName: String = "Emily"
}
fun main(args: Array<String>){
val user = User() // Note changed val from User to user.
print(user.userName) // Note, this really calls the getter
}
In Kotlin, a setter and getter is being created for every property (unless visibility prohibits it), e.g. for your userName which happens to be named exactly like the one you provided in addition: getUserName(). The result is a name clash. Note that for var, also setters are generated. Use val for read-only properties.
Actually, you don't need explicit getters like this. Simply do:
class User{
/*private*/ var userName: String = "Emily"
}
//use property syntax
val user = User()
print(user.userName)
I just want to add more about private field. When you declare a field with private modifier, Kotlin won't generate getter/setter
class User {
private var userName = "Na"
fun getUserName(): String {
return userName;
}
fun setUserName(v: String) {
userName = v
}
}
And when you declare above methods (getter and setter) then these are treated as User Defined methods.