Kotlinx Serialization for a generic sealed class (like an api result) - serialization

her is my sealed class
#Serializable
sealed class ApiResponse {
#Serializable
#SerialName("ApiResponse.Success")
data class Success<out T : Any>(val value: T) : ApiResponse()
#Serializable
#SerialName("ApiResponse.Failure")
object Failure : ApiResponse()
#Serializable
#SerialName("ApiResponse.InFlight")
object InFlight : ApiResponse()
}
I am getting this error when I call it
Can't locate argument-less serializer for class Success. For generic
classes, such as lists, please provide serializer explicitly.
I know I am missing something. Is there an easy way to serialize this? Also what is the value of inflight (saw this in an example, but I see tremendous value - I am using ktor for client and server)

Related

Kotlin serialization of value class that implements a sealed interface

I am trying to use Kotlin serialization (Kotlin 1.7.2, kotlinx.serialization 1.4.1) for value classes that implement a sealed interface:
#Serializable
sealed interface Power {
val value: Int
}
#Serializable
#JvmInline
value class PowerWatt(override val value: Int) : Power
#Serializable
#JvmInline
value class PowerHp(override val value: Int) : Power
When attempting serialization to Json, like so:
#Test
fun `Correctly serialize and deserialize a value class that implements a sealed interface`() {
val power: Power = PowerWatt(123)
val powerSerialized = Json.encodeToString(power)
val powerDeserialized = Json.decodeFromString<Power>(powerSerialized)
assertEquals(power, powerDeserialized)
}
I run into the following error:
kotlinx.serialization.json.internal.JsonDecodingException: Expected class kotlinx.serialization.json.JsonObject as the serialized body of Power, but had class kotlinx.serialization.json.JsonLiteral
at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:94)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:81)
at kotlinx.serialization.json.Json.decodeFromString(Json.kt:95)
How to make this work? Am I missing something?
The answer was provided in a Kotlin Serialization GitHub Issue here. For value classes, the wrapped underlying type is serialized directly. Hence, there is not wrapping JSON object where the type field for the polymorphic serializer could be inserted.

Kotlinx serialisation, common interface or type class

I am working on a plugin type system where 3rd parties will register classes that will expose data. I don't know exactly what the data will look like but I will enumerate these plugin instances collect the data and I would like to serialise it. A simplified version.
interface DataProvider {
fun getDataThatIsSerializable() : ???
}
What can i set the return type to so that I know that I will be able to serialise it with kotlinx serialisation. I cannot see any common interface that is injected into the class and given thet kotlin doesn't support type classes its not clear how to achieve what I am trying to do?
I considered something like this:
interface DataProvider {
fun getDataThatIsSerializable() : Pair<Any,KSerializer<*>>
}
but i could not pass this into the Json.encodeAsString functions, the types do not match
Are there any other options I can consider?
kotlinx.serialization doesn't like serializing things unless you can tell it exactly what you're working with.
Would it make sense for the DataProvider to be responsible for serializing its own data? Something like:
interface DataProvider {
fun getDataThatIsSerializable() : Any
fun encodeAsJsonString(data: Any) : String
}
#Serializable
data class Foo(val value: Int)
class FooDataProvider : DataProvider {
override fun getDataThatIsSerializable() : Any {
return Foo(7)
}
override fun encodeAsJsonString(data: Any): String {
return Json.encodeToString(Foo.serializer(), data as Foo)
}
}

Deserializing a generic type using gson in kotlin

I have a generic type Animal implemented as a sealed class that can be a Dog or a Cat.
sealed class Animal(val typeOfAnimal: String) {
data class Dog(val barkVolume: Int): Animal("dog")
data class Cat(val hasStripes: Boolean): Animal("cat")
}
According to http://jansipke.nl/serialize-and-deserialize-a-list-of-polymorphic-objects-with-gson/ you can deserialize an Animal by registering a RuntimeTypeAdapterFactory
val animalAdapterFactory = RuntimeTypeAdapterFactory.of(Animal::class.java, "typeOfAnimal").registerSubtype(Dog::class.java, Dog::class.java.qualifiedName).registerSubtype(Cat::class.java, Cat::class.java.qualifiedName)
gson = GsonBuilder().registerTypeAdapterFactory(animalAdapterFactory)
But if I try to deserialize an animal that looks like
jsonStr = "{barkVolume: 100}"
gson.fromJson(json, Animal::class.java)
RuntimeTypeAdapterFactory complains that it can't deserialize Animal as it does not dfine a field named "typeOfAnimal"
To my understanding typeOfAnimal is a field you add to differentiate the subtypes and not something you need in the json you are deserializing. Because my json is really coming from an api I cannot add the field.
typeOfAnimal is required because gson must know which class should choose to deserialize your json. There is no way to guess the type, but you can implement your own deserializator. In a custom deserializator you can implement logic such as:
if (jsonObject.get("barkVolume") != null) {
// retun Dog object
}

polymorphic kotlinx serialization fails on object after obfuscation

i have a polymorphic type that is implemented by objects and classes.
sealed interface Base
#Serializable
#SerialName("Sub")
class Sub(...) : Base
#Serializable
#SerialName("Obj")
object Obj : Base
i use this type with kotlinx.serialization
polymorphic(Base::class) {
subclass(Sub::class)
subclass(Obj::class)
}
this runs when there is no obfuscation, but when obfuscation is turned on, i get:
Serializer for class 'Obj' is not found. Mark the class as #Serializable or provide the serializer explicitly.
my proguard configuration regarding kotlinx.serialization is
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keepclasseswithmembers class .** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class my.package.**$$serializer { *; }
The problem is the generated serializer is different for objects and classes.
while in classes it is Sub$serializer in objects it is Obj$defaultSerializer$delegate, which slips through the Proguard rules.
While a good solution would be changing the Proguard rules to catch this case,
a usefull hack is to pass the serializer directly, instead of letting the polymorphic builder find it by the class.
polymorphic(Base::class) {
..
subclass(Obj.serializer())
}

Choosing sealed subclass based on field value in Kotlinx Serialization

Is there a way to distinguish between Successful and Failed responses based on the value of ok field in a response JSON?
#Serializable
sealed class Response {
#Serializable
data class Successful(
#SerialName("ok")
val ok: Boolean,
#SerialName("payload")
val payload: Payload
) : Response()
#Serializable
data class Failed(
#SerialName("ok")
val ok: Boolean,
#SerialName("description")
val description: String
) : Response()
}
So, for {"ok":true, "payload":…} I want to get Successful class, and for {"ok":false, "description":…} — Failed.
I know that there is similar question — Deserializing into sealed subclass based on value of field — but it uses type field, and I don't have any type discriminators in the JSON (the meaning of ok is not type discrimination (though it can be used that way with some hacks, probably))