In a Kotlin library, I have the following class that I want to serialize (preferably with kotlinx):
class A(
id: PropertyValue<String>,
archived: PropertyValue<Boolean>
) {
val id: String by id
val archived: Boolean by archived
constructor(
id: String,
archived: Boolean,
) : this(PropertyValue.Value(id), PropertyValue.Value(archived))
}
However, Json.encodeToString method won't work, because this class does not have any serializer. How can I serialize this class?
Related
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.
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")
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.
I love to use single-value types for e.g. IDs. How can I tell Kotlinx Serializer to consider such types as regular fields, not objects?
Example:
data class User(
val id: UserId,
val name: String,
)
should be serialized to:
{
id: 123,
name: "foo"
}
and UserId is just data class(val value: Int)?
EDIT: I guess I could use custom serializer for each Id class, but I really don't wanna repeat such code for each and every ID in my domain. Moreover, I am not sure how to write one; so far my effort is failing.
EDIT2: this is my attempt for writing such serializer:
open class DoctorIdSerializer : KSerializer<DoctorId> {
override val descriptor: SerialDescriptor = PrimitiveDescriptor("id", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: DoctorId) {
encoder.encodeInt(value.value)
}
override fun deserialize(decoder: Decoder): DoctorId {
return DoctorId(decoder.decodeInt())
}
}
basically, whats written here but its not working :(
I have 2 (data) classes that almost share the same properties:
data class Foo(
val id: FooId,
val name: String,
... 10+ properties
}
data class NewFoo(
val name: String,
... 10+ properties
}
I just want some syntax sugar magic here: not to repeat 10+ properties. I can make a base sealed class, but you would end up writing even more text (for passing arguments to base class ctor), although you are safer from making a mistake.
Yes, I know I could use composition for this, but here I don't want to, as there might be different 'variants' of the same data.
Am I missing something or this is not possible in Kotlin?
You can use an abstract (or sealed) class with abstract params instead and override them in the constructor of your data class (i.e. without additional passing them into the constructor of the base class).
abstract class Base {
// put commons parameter here
// abstract param needs to be initialized in the constructor of data class
abstract val name: String
// you can define some not-abstract params as well
open lateinit var someOtherParam: String
}
data class Foo1(
override val name: String,
val id: Int,
val someAdditionalParam1: String
) : Base()
data class Foo2(
override val name: String,
val someAdditionalParam2: String,
override var someOtherParam: String
) : Base()