Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do I specify a default serializer for polymorphic or sealed types #2594

Closed
ForteScarlet opened this issue Mar 6, 2024 · 7 comments
Closed

Comments

@ForteScarlet
Copy link

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.

@pdvrieze
Copy link
Contributor

pdvrieze commented Mar 6, 2024

@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

@ForteScarlet
Copy link
Author

ForteScarlet commented Mar 6, 2024

@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😂

@sandwwraith
Copy link
Member

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

@ForteScarlet
Copy link
Author

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?

@sandwwraith
Copy link
Member

sandwwraith commented Mar 12, 2024

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.

@ForteScarlet
Copy link
Author

@sandwwraith That's great! It seems that overwriteWith can solve that problem, thanks for letting me know 😉

@sandwwraith
Copy link
Member

Can be solved with #2199 / #1865: if SealedClassSerializer would expose its SerializersModule, it would be easier to add default deserializer to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants