Wrapping data classes in kotlin - kotlin

I wanted to ask about data classes in kotlin.
Suppose I have 2 data classes , data class A and data class B , I want to wrap my data class A around data class B.
Is this a case of Inheritance or inline class , I am kind of new to Kotlin .
data class A(
var x1:Float?,
var y1:Float?
)
data class B(
var x:Float?,
var y:Float?,
// other variables and methods
)
Thanks

You can't inherit a Kotlin data class from an other data class, but you can use interfaces or abstract classes and your data classes can implement/extend these:
abstract class A {
abstract var valueA: Int
}
interface B {
val valueB: Int
}
data class C(
val value2: String,
override var valueA: Int
) : A()
data class D(
override val valueB: Int,
val valueD: String
) : B

Related

Serialization with sealed classes fails in Kotlin serialization

I'm having trouble with kotlin-serialization in the following use case:
#Serializable
sealed class NetworkAnswer {
#SerialName("answerId")
abstract val id: Int
}
#Serializable
data class NetworkYesNoAnswer(
override val id: Int,
#SerialName("isPositive")
val isPositive: Boolean
) : NetworkAnswer()
When I serialize this:
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
val result: NetworkYesNoAnswer = json.decodeFromString(NetworkYesNoAnswer.serializer(), """
{
"answerId": 1,
"isPositive": true
}
""".trimIndent()
)
I get the following error
Caused by: kotlinx.serialization.MissingFieldException: Fields [id] are required for type with serial name 'NetworkYesNoAnswer', but they were missing
The only way the serialization works is if I use the same name for both the member and "SerialName", like so:
#Serializable
sealed class NetworkAnswer {
#SerialName("answerId")
abstract val answerId: Int
}
#Serializable
data class NetworkYesNoAnswer(
override val answerId: Int,
#SerialName("isPositive")
val isPositive: Boolean
) : NetworkAnswer()
This kinda defeats the purpose of "SerialName", is there a way to solve that without using the same name?
Declaring a #SerialName on a base class has no effect on member declarations overridden by child classes.
Instead, you can declare #SerialName on the child class instead. There is no need to change the actual name of the field.
#Serializable
data class NetworkYesNoAnswer(
#SerialName("answerId")
override val id: Int,
#SerialName("isPositive")
val isPositive: Boolean
) : NetworkAnswer()
Declaring the #SerialName on the base class and applying it to all children seems NOT to be supported as of now, but is desired by other members of the community as well, e.g. here on GitHub.
OT: Most likely you could use a sealed interface, which was first introduced in Kotlin v1.5.0, instead of a sealed class.

What is the difference for each case of this sealed class?

While looking at the sample code to use the sealed class, I saw several cases in which the method of using the sealed class was different.
case1 is similar to enum class, but I know that it can create multiple instances.
But what makes case2 different?
And I'm wondering what's the difference between inheriting from a normal class(or interface)
case1
The first case is the most common sealed class method in the sample code.
sealed class Parent(
val t1: String,
val t2: String,
) {
data class A(
val id: String,
val title: String,
val num : Int
) : Parent(
t1 = id,
t2 = title,
) { }
data class B(
val id: String,
val title: String,
) : Parent(
t1 = id,
t2 = title,
) { }
}
case2
The second case is a case where you are curious about the difference from inheriting a normal class.
sealed class Parent(
val t1: String,
open val t2: String,
) { }
data class A(
val id: String,
val title: String,
val num : Int
) : Parent(
t1 = id,
t2 = title,
) { }
data class B(
val id: String,
val title: String,
) : Parent(
t1 = id,
t2 = title,
) { }
open class Parent( // or interface
val t1: String,
open val t2: String,
) { }
data class A(
val id: String,
val title: String,
val num : Int
) : Parent(
t1 = id,
t2 = title,
) { }
data class B(
val id: String,
val title: String,
) : Parent(
t1 = id,
t2 = title,
) { }
Case 1:
uses nested classes.
was the required way to do sealed classes in Kotlin before 1.5.
suggests a direct relationship between the nessted and outer class that you might want to carry throughout your code for clarity (like PaymentType.CreditCard, PaymentType.Checking, etc), so can be used as a grouping / organizing strategy
requires scoping when declaring fields (val paymentMethod = PaymentMethod.CreditCard(...)) or adding extra imports
Case 2:
no more nested classes
only possible as of Kotlin 1.5
suggests a less direct relationship between the base and derived classes that doesn't require being maintained throughout the code (like Animal, Cat, Dog, etc)
Does not require scoping or extra imports (val cat = Cat())
Finally:
And I'm wondering what's the difference between inheriting from a normal class(or interface)
The Kotlin docs are pretty clear on sealed classes and regular classes. The key point for sealed classes being:
Sealed classes and interfaces represent restricted class hierarchies that provide more control over inheritance. All direct subclasses of a sealed class are known at compile time. No other subclasses may appear after a module with the sealed class is compiled. For example, third-party clients can't extend your sealed class in their code. Thus, each instance of a sealed class has a type from a limited set that is known when this class is compiled.
In short: they're enums on crack. (PSA: don't do crack).

can I use kotlinx serializer with multiple sealed class levels as parents and a nested invocation?

I am trying to use kotlinx #Serializable and Ive faced this issue:
I have the following classes:
#Serializable
sealed class GrandParent
a second one:
#Serializable
sealed class Parent() : GrandParent() {
abstract val id: String
}
and a third one
#Serializable
data class Child(
override val id: String, ....
): Parent()
I'm needing of grandparent since I use it as a generic type in another class, which happen to also have a reference to the GrandParent class
#Serializable
data class MyContent(
override val id: String,
....
val data: GrandParent, <- so it has a self reference to hold nested levels
...): Parent()
Every time I try to run this I get an error...
Class 'MyContent' is not registered for polymorphic serialization in the scope of 'GrandParent'.
Mark the base class as 'sealed' or register the serializer explicitly.
I am using ktor as wrapper, kotlin 1.5.10. I did this based on https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#registered-subclasses
Any ideas?
You should serialize and deserialize using your sealed class in order for kotlin serialization to "know" to add a discriminator with the right implementation. By default it search for type in the json but you can change it with JsonBuilder:
Json {
classDiscriminator = "class"
}
Here is an example:
#Serializable
sealed class GrandParent
#Serializable
sealed class Parent : GrandParent() {
abstract val id: String,
}
#Serializable
data class Child(
override val id: String,
): Parent()
#Serializable
data class MyContent(
override val id: String,
val data: GrandParent,
): Parent()
fun main() {
val test = MyContent(id = "test", data = Child(id = "child"))
val jsonStr = Json.encodeToString(GrandParent.serializer(), test)
println("Json string: $jsonStr")
val decoded = Json.decodeFromString(GrandParent.serializer(), jsonStr)
println("Decoded object: $decoded")
}
Result in console:
Json string: {"type":"MyContent","id":"test","data":{"type":"Child","id":"child"}}
Decoded object: MyContent(id=test, data=Child(id=child))
encode and decode can also be written like this (but behind the scenes it will use reflections):
val jsonStr = Json.encodeToString<GrandParent>(test)
println("Json string: $jsonStr")
val decoded = Json.decodeFromString<GrandParent>(jsonStr)
println("Decoded object: $decoded")

What are the best practices to unify access to data classes properties

I have a class ExpenseDto
data class ExpenseDto(
val id: Int,
val name: String,
val aggregationA: ExpenseAggregationA,
val aggregationB: ExpenseAggregationB,
val aggregationC: ExpenseAggregationC,
)
And all its associations have the same fields. And what are the best practies that can be applied here to universalize it
data class ExpenseAggregationA(
val id: Int,
val text: String? = null
)
data class ExpenseAggregationB(
val id: Int,
val text: String? = null
)
data class ExpenseAggregationC(
val id: Int,
val text: String? = null
)
data classes can inherit from a sealed class.
sealed class ExpenseAggParent (val id: Int, val text: String? = null) {
data class ExpAggA( override val id: Int, override ...)
data class ...
data class ...
}
Besides that, sometimes I like to have "property unifier" interfaces, especially when refactoring a legacy code with classes with the same semantics but different names of the properties:
interface HasId(val id: Int)
data class ExpenseAggregationX(
override val id: Int,
...
): HasId
data class ExpenseAggregationY: HasId {
val someOtherNameButStillId: Int
override val id: Int
get() = this.someOtherNameButStillId
}
And, of course, you can use the good old interface, but for that, you may re-use the above approach:
interface ExpenseAggregation: HasId, HasText
data class ExpenseAggregationA(
override val id: Int,
...
): ExpenseAggregation
// Other approach for classes with other existing names, see above
data class ExpenseAggregationB: ExpenseAggregation {
...
}
This is close to a mix-in approach which Kotlin does not currently support directly, but for completeness, there are delegations usable if your class is rather a service than a DTO.

Kotlin sealed classes assigning property constants

kotlin_version = '1.2.30'
I have a sqlite table that has a Integer value for a column called direction. That will store the Integer property based on the enum constant.
i.e will insert 40 into the table:
saveDirection(Direction.Right.code)
I have a enum class written in kotlin with property constants assigned.
enum class Direction(val code: Int) {
UP(10),
DOWN(20),
LEFT(30),
RIGHT(40),
NONE(0)
}
I am wondering if I can do the same with sealed classes
sealed class Direction {
abstract val code: Int
data class Up(override val code: Int): Direction()
data class Down(override val code: Int): Direction()
data class Left(override val code: Int): Direction()
data class Right(override val code: Int): Direction()
data class None(override val code: Int): Direction()
}
However, this won't work as the saveDirection(direction: Int) is expecting an Int value:
saveDirection(Direction.Right(40))
Is this possible to assign constant properties to sealed classes so that you can get the constant property like in enums?
Thanks for any suggestions,
You could use sealed classes like this:
sealed class Direction(val code: Int) {
override fun equals(other: Any?): Boolean = other is Direction && code == other.code
override fun hashCode(): Int = code
}
class Up : Direction(10)
class Down : Direction(20)
class Left : Direction(30)
class Right : Direction(40)
class None : Direction(0)
However, given the limited context of the question, it is unclear what exactly you would gain by this. In fact, in this simple case, Kotlin does not allow your subclasses to be marked as data class:
Data class must have at least one primary constructor parameter
The solution provided above does not have any advantage over enums, in fact it is more verbose and error-prone than an enum-based definition, so why not just use those?