kotlinx.serialization
kotlinx.serialization copied to clipboard
How do I specify a default serializer for polymorphic or sealed types
What is your use-case and why do you need this feature?
hello. Suppose I have an abstract type 'Command' that can be extended by third parties.
I want it to have a default deserialization type target that doesn't require SerializersModule to work.
It can be deserialized by any SerialFormat, not just JSON.
And I want this default to have a lower precedence than the one in SerializersModule, which means it can be overridden with SerializersModule.polymorphic(...) { defaultDeserializer { ... } }.
@Serializable
// I want something where can specify the default serialization type.
// @PolymorphicDefault(DefaultCommand::class)
sealed class Command
@Serializable
@SerialName("c1")
data class Command1(val name: String) : Command()
@Serializable
@SerialName("c2")
data class Command2(val size: Long) : Command()
// Extensible to third parties
@Serializable
abstract class CustomCommand : Command() {
abstract val code: Int
}
@Serializable
// Maybe here?
// @PolymorphicDefault(Command::class)
data object DefaultCommand : Command()
fun decodeCommand(format: StringFormat, raw: String): Command =
format.decodeFromString(Command.serializer(), raw)
Describe the solution you'd like
I want to be able to specify a default serializer for a polymorphic type without additional conditions (such as having to configure SerializersModule).
Maybe provide some annotations to specify a default serialisable type at compile time? Like @PolymorphicDefault in the code used as an example above.
@ForteScarlet I think you are confused about SerializersModule. That is format independent, and the way to specify how polymorphic and contextual serializers are resolved. If you want a hierarchy you can apply that in the implementation for defaultDeserializer
@pdvrieze But this Command may need to be provided for external use, so I may not be able to control their SerializersModule. I want to have a default serializer when some external consumer deserializes it.
I think I roughly know that SerializersModule has nothing to do with structure, but I saw that Content-based polymorphic deserialization seems to be able to customize a serializer to some extent with a default serializer, but it might not quite satisfy the 'wish' I mentioned, so I mentioned itπ
There's no way to set up a default deserializer with annotation. To distribute your polymorphic serializable classes to clients, you have to distribute SerializersModule with them, so clients can extend it. SerializersModule support combination, see here: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#merging-library-serializers-modules
Okay, but if I distribute the SerializersModule along with classes, I can't seem to provide a default serializer and allow the clients to redefine it.
@Serializable
abstract class Base
@Serializable
class T1 : Base()
@Serializable
class T2 : Base()
@Test
fun serialTest() {
val data = "{}"
// My distribution
val defaultModule = SerializersModule {
polymorphicDefaultDeserializer(Base::class) { T1.serializer() }
}
// Clients
val clientModule = SerializersModule {
include(defaultModule)
polymorphicDefaultDeserializer(Base::class) { T2.serializer() }
// π java.lang.IllegalArgumentException: Default deserializers provider for class SerTest$Base is already registered: (kotlin.String?) -> kotlinx.serialization.DeserializationStrategy<SerTest.Base>?
}
val json = Json {
isLenient = true
serializersModule = clientModule
}
val decoded = json.decodeFromString(Base.serializer(), data)
assertIs<T2>(decoded)
}
Does that mean I have to rely on some documentation warning or suggestion that clients can add T1 as the default serializer, but I can't give it to them directly?
It seems our documentation is lacking in this place. There's a special overwriteWith (https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/overwrite-with.html) function for modules:
val defaultModule = SerializersModule {
polymorphicDefaultDeserializer(Base::class) { T1.serializer() }
}
val clientModule = defaultModule overwriteWith SerializersModule {
polymorphicDefaultDeserializer(Base::class) { T2.serializer() }
}
does what you want.
@sandwwraith That's great! It seems that overwriteWith can solve that problem, thanks for letting me know π