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

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.

Related

Kotlin best-practice for domain model class inheritance

This is more of a theoretical Kotlin question about inheritance in relation to domain model classes.
Let's consider this scenario:
I'm building a system to handle books at a library so obviously some kind of book class is needed. I can create this easily with a data class:
data class Book (val title: String, val author: String)
So far so good. Now, this system also handles book reservations which will be a different model specifying pickup location and pickup date, e.g.
data class ReservedBook(val title: String, val author: String, val location: String, val pickupDate: String)
So, of course the ReservedBook class overlaps with the Book class, so I'm thinking what the best approach is to make this a bit more maintainable and understandable.
My initial though was to just make the ReservedBook inherit from the Book class, but Kotlin does not allow data classes to inherit from other data classes as it screws up the constructor/get/set methods.
The easy solution seems to include the Book as a property in the ReservedBook, e.g.
data class ReservedBook(val book: Book, val location: String, val pickupDate: String)
Easy enough, although a bit weird when creating an instance of a ReservedBook that you need to specify the Book inside, e.g.
val newReservedBook = ReservedBook(Book("MyTitle", "MyAuthor"), "Location B", "20-10-2022")
Hence, I was thinking if there was a smarter way of constructing such a setup. I was thinking of creating an abstract class called BaseBook or something like that and then have all the data classes that are more specific inherit from this, although writing the classes seem a bit cumbersome:
abstract class BaseBook(
open val title: String,
open val author: String
)
data class ReservedBook(
override val title: String,
override val author: String,
val location: String,
val pickupDate: String
) : BaseBook(title, author)
This, however, makes it a lot easier to create new instances as you can just write:
val newReservedBook = ReservedBook("MyTitle", "MyAuthor", "Location B", "20-10-2022")
So, I'm curious as what people think would be the best way of handling a situation like this. Maybe you also have a different proposal?
I think #TheLibrarian made a good point in the comments on the original question. Stating that it is weird to make a new book for a reservation.
To achieve this concept, we use some of Kotlin's most powerful tools.
Specifically, this is to make interfaces that operate as Traits (This is based in Scala but the concept applies)
For this kind of design, writing our code to interfaces rather than implementations enables us to design very freely within our applications.
/** Interface for Books called IBook
* We define our [title] and [author] as you would expect
* We also utilize the power of a trait to give us an easy constructor for creating an [IReservedBook]
*/
interface IBook {
val title: String
val author: String
fun reserve(location: String, pickupDate: String): IReservedBook {
return ReservedBook(this, location, pickupDate)
}
}
/** Interface for reserved Books, which are [IBooks]
* We define our [location] and [pickupDate] without having to redeclare from [IBook]
*/
interface IReservedBook : IBook {
val location: String
val pickupDate: String
}
/** The actual class for [IBook] very straight forward */
data class Book(
override val author: String,
override val title: String
) : IBook
/** The actual class for [ReservedBook] where the magic starts to happen
* First, we take in an already existing instance of [IBook] - We want to develop around the interface rather than the
* actual to keep ourselves flexible. This helps us if we have multiple kinds of Books such as a PictureBook or a Reference
*
* Second, we override our base interface for the [IReservedBook]
*
* Next we specify how this actually fulfills [IBook]
* by using the `IBook by Book(book.author, book.title)`
*
* This gives us the best of both worlds by making sure a Book can become a ReservedBook, we also do not need to create
* a new instance of a Book to make it Reserved, and we maintain the freedom to introduce new books, as well as reservations
* without having any impact on our previous code.
*/
data class ReservedBook(
val book: IBook,
override val location: String,
override val pickupDate: String
) : IReservedBook, IBook by Book(book.author, book.title)
fun library() {
val importantBook = Book("A very impressive person", "Kotlin Rules")
val importantReservation = ReservedBook(importantBook, "Nimbus", "Tomorrow")
val popularBook = Book("Dracula", "20 ways to avoid garlic")
val popularReservation = popularBook.reserve("Transylvania", "Right now")
}
This structure builds and allows very well for the composition that Kotlin encourages, as well as keeping things simple and smooth to make expansive domains for information.

How to create a table(in RDBMS) of a data class in Kotlin?

I have a data class PostInfo.kt in Kotlin like this:
#DBUniqueKeyColumns(["postId"])
data class PostInfo{
val postId: Int,
#DBDataType("varchar(64)")
val postContent: String,
#DBDataType("varchar(10)")
val postAuthor: String,
}
Say, I have a DbHost and I want to run some SQL commands(CRUD), say like this:
fun createTable(tableName: string){
myDbConnection.executeSqlQueryUsingJDBC("create table "+tableName);
// or a better way to use the data class and create a table.
}
What are the ways to do it?
In other words. If I have a data class defined like above in Kotlin, is there a way I can use it directly to create, delete, manipulate SQL Database?
With an Object/Relational Mapper is this possible, like Exposed, an ORM framework for Kotlin:
https://github.com/JetBrains/Exposed
or Hibernate (hibernate.org/orm) or Spring Boot/Spring Data (spring.io/projects/spring-data-jpa)
With Spring Boot a class looks like this:
#Entity
class User(
var login: String,
var firstname: String,
var lastname: String,
var description: String? = null,
#Id #GeneratedValue var id: Long? = null)
More information about Kotlin with Spring Boot
https://spring.io/guides/tutorials/spring-boot-kotlin/

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)

Implementing a type-safe class hierarchy w/ a nullable value

I (often) have a resource with two states, pre-created and post-created, where both states have the same fields except for an id field. id is null in the pre-created state and non-null in the post-created state.
I would like to define and use this resource in a clean and type-safe way.
It's common to represent this ID field as a nullable, which handles both scenarios with minimal boilerplate in the class definition. The problem is that it creates a lot of boilerplate in the business logic because you can't assert whether a resource is pre-created or post-created by looking at its type.
Here is an example of the nullable approach:
data class Resource(val id: String?, val property: String)
This is simple to define, but not as simple to handle with due to lack of compile-time guarantees.
Here's an example of a more type-safe approach:
sealed class Resource(val property: String) {
class WithoutID(property: String): Resource(property)
class WithID(val id: String, property: String): Resource(property)
}
This allows me to pass around Resource.WithID and Resource.WithoutID, which have all the same fields and methods, except for id.
One inconvenience with this type-safe approach is that the resource definition code gets quite bloated when you have many property fields. This bloating makes the code harder to read.
I'm wondering if there's an alternative approach with less boilerplate, or if Kotlin has any features that make this kind of thing simpler.
What about defining
sealed class MayHaveId<T> { abstract val record: T }
class WithId<T>(val id: String, override val record: T): MayHaveId<T>()
class WithoutId<T>(override val record: T): MayHaveId<T>()
class Resource(val property: String)
// and other similar types
and using WithId<Resource> and WithoutId<Resource>? In Scala you could add an implicit conversion from MayHaveId<T> to T, but not in Kotlin, alas, nor can you write : T by record. Still should be clean enough to use.
One of the options is to get into composition relying on properties inside interfaces.
interface Resource {
val property: String
}
interface WithId : Resource {
val id: Int
}
interface WithOtherField : Resource {
val otherField: Any
}
class WithoutIdImpl(override val property: String) : Resource
class WithIdImpl(override val id: Int, override val property: String) : WithId
class WithIdAndOtherField(
override val id: Int,
override val otherField: Any,
override val property: String) : WithId, WithOtherField
I didn't get from your example, how you're going to switch between two states of Resource. So probably there is a gap to overcome.
Probably, Smart casts will allow to switch states.

Kotlin data class with additional properties not in constructor

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.