Kotlin data class with additional properties not in constructor - kotlin

Starting out with Kotlin and wanting to make a data class
data class Person(val Email: String, val firstName: String, val lastName: String)
But let's say I want to add additional properties that I don't know at the time when I am using the constructor but I want to store this data at a later point when I am aware of it for example a person's mood (Represented as a String)
In Java I would make a data class like this. I would be able to not include it in the Constructor and make a getter where I could set it at a later time.
public class Person{
private String email;
private String firstName;
private String lastName;
private String mood;
public person (String email, String firstName, String lastName){
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
}
public setMood(String mood){
this.mood = mood;
}
}
Kotlin doesn't appear to have an answer on this or if it does I do not know how to phrase correctly. Hence why this question could already be answered and I am unable to find it.
I do understand that by not including mood in the data class line Kotlin may not be able to identify mood as part of the data class but aside from including it in the constructor and setting it to null I'm not sure what else to do or is that what I am supposed to do?

You should be able to just add it as a property to Person. In Kotlin, a data class is still a class, it just comes with some extras (toString, copy constructors, hashCode/equals, etc). You can still define any properties that you want.
data class Person(val Email: String, val firstName: String, val lastName: String) {
var mood: String? = null
}
In this case it is nullable, because as you stated, you might not know the mood until later.

Kotlin's data class must have first constructor, you can avoid it by not using the data keyword.
If you still want to add another property to the data class you can do the following:
data class Person(val email: String, val firstName: String, val lastName: String){
var mood: String = ""
}
This way you can do person.mood = "happy" without including it in the constructor.

Kotlin only considers the values passed to the primary constructor in terms of giving you the "for free" features that a Data class provides. Beyond that, you can add whatever additional properties you desire, but they aren't accounted for in the special code that Kotlin writes by way of you marking a class as data.
Per the Kotlin docs:
Note that the compiler only uses the properties defined inside the
primary constructor for the automatically generated functions. To
exclude a property from the generated implementations, declare it
inside the class body:
Per this, declaring properties outside of the primary constructor actually has benefits. You might be able to declare a property via the primary constructor, but choose not to.
Not only do you have to provide a primary constructor, but it has to include at least one property declaration. If you didn't do this, there would be no benefit to making the class a data class. But marking a class so does not limit what else you can do with that class.

Have you tried:
data class Person(val Email: String, val firstName: String, val lastName: String) {
var mood: String? = null
}

An alternative to #Todd's and #jingx's answers is
data class Person(val Email: String, val firstName: String, val lastName: String, var mood: String? = null)
The difference is that this way mood participates in toString/equals/hashCode/copy and that you can set mood in the constructor call. Even if that's probably not desirable for this specific case, it can be useful in others.

Related

Map properties from data ClassA to data ClassB if both inherit same interface

I am working with more complex classes so I try put there simple example what I need.
I have this interface:
interface BaseUser {
val firstName: String
val lastName: String
}
and these 2 data classes:
data class UserA(
override val firstName: String,
override val lastName: String,
): BaseUser
data class UserB(
override val firstName: String,
override val lastName: String,
val someComputedProperty: Int
): BaseUser
I already wrote mapper to map a database entity User into UserA, something like that:
fun UserEntity.toDto(): UserA {
return UserA(firstName = firstName, lastName = lastName)
}
I also need have similar method which map database entity to UserB and also compute value for aditional property. In real code I have a lot of properties in classes not just 2, so I am thinking how to reuse code, and use this mapping for UserB in some way, and also be able compute then from entity additional fields.
Is in Kotlin any elegant way to covert UserA into UserB thanks to interface or something else? Thank you.
Although both data classes are implementing the same interface class, there is no built-in function to directly convert these classes directly. Because there is actually no relationship between data classes implementing the same interface class.
In order to convert one class to another, you can probably refer to this previous post.

How to serialize an inner class as a single value with GSON

Assume the following two clases
class Person internal constructor(var name: String, var address: Address)
class Address internal constructor(var city: String, var postcode: String)
The JSON serialization of this is
{"name":"Bob","address":{"city":"London","postcode":"LO12 345"}}
On the API output, I want to serialise the address as a single string, and not an object. e.g. {"name":"Bob","address":"London, LO12 345"}
I can achieve this if I write a custom serializer for Person, and even managed to work around the issue of only changing this single attribute instead of everything. However, I still have the issue that I now need to change how every single class that uses Address.
I am wondering if instead, there is a way to writte a serializer for Address that would only return a value instead of an object, much like a primive does. Then I am hoping that every single class that uses Address, would behave as if Address was a String straight away and serialize it as such.
Is such a thing possible?
You can write a serialiser for Address like this:
class AddressSerialiser: JsonSerializer<Address> {
override fun serialize(p0: Address, p1: Type?, p2: JsonSerializationContext?) =
JsonPrimitive("${p0.city}, ${p0.postcode}")
}
This serialiser outputs a JsonPrimitive, which is a string containing the city and postcode.
You can then register this when creating a Gson:
val gson = GsonBuilder()
.registerTypeAdapter(Address::class.java, AddressSerialiser())
.create()
println(gson.toJson(Person("John", Address("Cambridge", "CB5 8HX"))))
Output:
{"name":"John","address":"Cambridge, CB5 8HX"}
Not exactly sure if this is what you're looking for but what if you simply make it a data class? Like
data class Address internal constructor(var city: String, var postcode: String)
Then the toString() of it is a very descriptive string, and similar objects also count as equal, like
val a = Address("London", "LO12 345")
val b = Address("London", "LO12 345")
println(a) //prints: Address(city=London, postcode=LO12 345)
println(a == b) //prints: true

Kotlin inc() operator overloading

I have a little problem to overload inc() operator, precisely to make a postfix and a prefix one.
Here my data class
data class Person(val firstName: String, val name: String, var age: Int) {
operator fun inc(): Person {
val tmp = this
this.age++;
return tmp
}
}
With this, age change before returning so it's only working for prefix version.
How can I do a postfix version of inc() operator ?
inc is expected to return a new, incremented instance of the class. Since you've got a dataclass, we can use Kotlin's convenience functions that work on dataclasses to get a new instance for you relatively effortlessly.
data class Person(val firstName: String, val name: String, var age: Int) {
operator fun inc(): Person =
this.copy(age = this.age + 1)
}
Person.copy is one of several dataclass methods generated for you. It takes the same arguments as your primary constructor, with each argument defaulting to the current value on this (i.e. any arguments not passed will be the same as the corresponding values on this). So by passing only the age parameter by name, we modify only the one we want to and leave the others untouched.
There is no way to do what you’re trying to do. You are breaking the contract that the increment operator must not mutate the class. It must return a new instance of the class.

Kotlin data classes with Java super class

I have a Java class that holds generic information on databse entities (i.e. their id).
#Data
public class DbEntity {
protected final String id;
public DbEntity(String id) {
this.id = id;
}
}
We use Lombok #Data to generate getters, toString, equals...
In Java I would simply extend this class and add #Data once again.
#Data
class JavaSubClass extends DbEntity {
public JavaSubClass(String id) {
super(id);
}
}
In a newer service we use Kotlin but would like to reuse standard classes such as DbEntity.
My first approach was to simply declare a data class such as
data class SubClass1(val id: String, val name: String) : DbEntity(id)
Accidental override: The following declarations have the same JVM signature (getId()Ljava/lang/String;):
fun <get-id>(): String defined in com.demo.SubClass1
fun getId(): String! defined in com.demo.SubClass1
After some reading I found several solutions, all of which I'm not super happy with.
Don't use data classes. This works but leaves me with the task of implementing equals etc.
class SubClass4(id: String, val name: String) : DbEntity(id)
Duplicate the field. This works but we end up with two fields that could go out of sync.
data class SubClass3(val subId: String, val name: String) : DbEntity(subId)
Assign a different name to the getter. This fundamentally also duplicates the field, but hides the getter.
data class SubClass2(#get:JvmName("getId_") val id: String, val name: String) : DbEntity(id)
As I said, I'm not happy with any of the solution presented above. Having an abstract super class or an interface instead would certainly be more appropriate. However the Entity class resides in a library that primarily Java projects depend on. I'm hesitant to change it just because of a new Kotlin dependnecy.
Did anyone encounter similar issues and has advice on how to solve them?
As a workaround, until KT-6653 - Kotlin properties do not override Java-style getters and setters is fixed, I would go for a variant of your point 3, i.e.:
data class SubClass(#get:JvmName("bogusId") private val id: String, val name: String) : DbEntity(id)
The benefit of this variant is, that you always access the "original" getId-function. You will not use the bogusId()-function as it is not visible/accessible (accessing it via reflection makes no sense... you are only interested in the actual id-field). This works and looks similar for both sides: from Java as also from Kotlin. Still, under the hood this variant uses 2 fields, but in the best case you can just replace it in future with something like:
data class SubClass(override val id: String, val name : String) : DbEntity(id)

Secondary construction syntax kotlin

I have the following kotlin class with a primary constructor,
class Person(first: String, last: String, age: Int){
init{
println("Initializing")
}
}
I'd like to add a secondary constructor that parses a fullname into a first and last name and calls the primary constructor. However, I can't get the syntax right...
class Person(first: String, last: String, age: Int){
// Secondary constructor
constructor(fullname: String, age: Int):
this("first", "last", age)
{
println("In secondary constructor")
}
init{
println("Initializing")
}
}
This works fine, because I'm not actually parsing fullname in the secondary constructor. When I go ahead and try to parse fullname,
constructor(fullname: String, age: Int):
var first = fullname.split()[0];
...
{
println("In secondary constructor")
}
I get an unresolved reference: fullname. It doesn't exist in scope, but if I put it in the braces, then I cannot call the primary constructor via this,
constructor(fullname: String, age: Int):
{
var first = fullname
this(first, "foo", age)
println("In secondary constructor")
}
I get an error involving a missing invoke function.
Can't find a good example of this case on Kotlin docs, sorry.
The solution I use when I want a secondary constructor that needs to perform some calculations before passing the results to the primary constructor is to a function on the companion object. The code to do this would look like:
class Person(first: String, last: String, age: Int) {
companion object {
fun fromFullNameAndAge(fullname: String, age: Int) : Person {
println("In secondary constructor")
var bits = fullname.split()
// Additional error checking can (and should) go in here.
return Person(bits[0],bits[1],age)
}
}
init{
println("Initializing")
}
}
You can then use it like this
var p = Person.fromFullNameAndAge("John Doe", 27)
Which is not as neat as Person("John Doe", 27) but is IMO not too bad.
Constructor calls via this must be the first call. This is why it's handled as a delegate, rather than a normal method invocation. This means you cannot declare variables before the call is delegated.
You can solve this by simply inlining whatever values you planned on storing in variables:
constructor(fullName : String, age : int) : this(fullName.split(" ")[0], fullName.split(" ")[1])
But this can potentially index out of bounds if a last name wasn't specified, or if the client decided to use - or some other character as the delimiter. On top of that, it's an eye sore.
Design Analysis
The issue with your structure is giving the Person class the responsibility of determining the first and last name. This deteriorates the reusability of that class, as it'll be limited to one form of parsing. This is why the parsing of names should not be carried out by Person.
Instead, you should expose your primary constructor, then have the client of Person separate the first and last name.
Solution Example
Imagine we were reading names from a file. Each line in the file consists of a full name.
nameFile.forEachLine({ personList.add(Person(it)) })
This is the luxury you are attempting to give your clients: allow them to simply input a name, without worrying about parsing it.
The problem with this is the lack of safety: what if the line only contained a first name? What if the file didn't use whitespace to separate first and last name? You'd be forced to define new Person types just to handle different first/last name combinations.
Instead, the parsing should occur outside of the class:
file.forEachLine({
val firstName = ...
val secondName = ...
personList.add(Person(firstName, secondName))
})
Now that the responsibility has been taken out of Person, we can give the responsibility to a new object if we wanted:
val parser = NameParser(" ") //specify delimiter
file.forEachLine({
val firstName = parser.extractFirstName(it)
val lastName = parser.extractLastName(it)
personList.add(Person(firsrName, lastName))
})