How to serialize kotlin sealed class with open val using kotlinx serialization - kotlin

import kotlinx.serialization.Serializable
#Serializable
sealed class Exercise(open val id: String) {
#Serializable
data class Theory(override val id: String) : Exercise(id)
}
I have such kind of sealed class in my code, and compiler says me:
Serializable class has duplicate serial name of property 'id', either in the class itself or its supertypes.
Is there way to have open val in serializable sealed class, which works correctly when overriding it?

This is Kotlin issue KT-38958. It seems to be a corner case of the Constructor properties requirement.
It can be solved by using the following implementation,
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
#Serializable
sealed class Exercise {
abstract val id: String
#Serializable
data class Theory(override val id: String) : Exercise()
}
fun main() {
val t1 = Exercise.Theory("t1")
val t1Json = Json.encodeToString(t1)
println(t1Json)
println(Json.decodeFromString<Exercise.Theory>(t1Json).toString())
}
which will output:
{"id":"t1"}
Theory(id=t1)
For details, see "Designing serializable hierarchy" in the Kotlin Serialization Guide.

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.

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

Proper way to serialize a sealed class with kotlinx-serialization

I am not sure if it is possible yet but i would like to serialize the following class.
#Serializable
sealed class RestResponseDTO<out T : Any>{
#Serializable
#SerialName("Success")
class Success<out T : Any>(val value: T) : RestResponseDTO<T>()
#Serializable
#SerialName("Failure")
class Error(val message: String) : RestResponseDTO<String>()
}
when i try and use it
route(buildRoute(BookDTO.restStub)) {
get {
call.respond(RestResponseDTO.Success(BookRepo.getAll()))
}
}
I get this error:
kotlinx.serialization.SerializationException: Serializer for class
'Success' is not found. Mark the class as #Serializable or provide the
serializer explicitly.
The repo mentioned in the get portion of the route returns a list of BookDTO
#Serializable
data class BookDTO(
override val id: Int,
override val dateCreated: Long,
override val dateUpdated: Long,
val title: String,
val isbn: String,
val description: String,
val publisher:DTOMin,
val authors:List<DTOMin>
):DTO {
override fun getDisplay() = title
companion object {
val restStub = "/books"
}
}
This problem is not a deal breaker but it would be great to use an exhaustive when on my ktor-client.
Serializing sealed classes works just fine. What is blocking you are the generic type parameters.
You probably want to remove those, and simply use value: DTO. Next, make sure to have all subtypes of DTO registered for polymorphic serialization in the SerializersModule.

How to have a variable in Kotlin to accept any subclass of given class

I want this
data class ActivityInfo(title: String, activity: Activity::class.java)
That I can hopefully use with subclasses of Activity
val mainActivityInfo= ActivityInfo("main activity",MainActivity::class.java) //where MainActivity extends Activity
How do I do this in kotlin
import android.app.Activity
data class ActivityInfo(val title: String, val activity: Class<out Activity>)
class YourActivity : Activity()
Usage
ActivityInfo("name", YourActivity::class.java)

Is Kotlin data class serializable by default?

After knowing Kotlin, love the data class.
I could replace Java classes that has equal and hash and toString to it.
Most of these Java classes are serializable class. So my question is, when we convert to data class, do I still need to make it serializable explicitly? like
data class SomeJavaToKotlinClass(val member: String) : Serializable
Or it is okay to be
data class SomeJavaToKotlinClass(val member: String)
No, Kotlin data classes do not implicitly implement this interface. You can see from this example:
import java.io.Serializable
data class Foo(val bar: String)
fun acceptsSerializable(s: Serializable) { }
fun main(args: Array<String>) {
val f: Foo = Foo("baz")
acceptsSerializable(f) // Will not compile
}
I had to add : Serializable at the end of class to make is Serializable. Just like this
class SizeVariantModel (val price: Double, val discountedPrice: Double?) : Serializable
class ColorVariantModel (val name: String, val colorCode: String) : Serializable
I also had to import Serializable
import java.io.Serializable