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

Incorrect SerialName used when using delegate serializer on a member of a sealed interface #2596

Closed
FlorianDenis opened this issue Mar 6, 2024 · 4 comments
Assignees

Comments

@FlorianDenis
Copy link

FlorianDenis commented Mar 6, 2024

Describe the bug

I have the suspicion that the incorrect SerialName is used when using a delegate serializer on a class implementing a sealed interface.
This could just be a configuration issue, but after carefully reading the doc, I cannot figure out what I am doing wrong.

To Reproduce
I have JSONs of the form

{"tag": "A"}
{"tag": "B", "field1": "someString"}
{"tag": "C", "aField": "someString", "anotherField": 42}

I am trying to build a sealed class hierarchy to read/write those here, only I want each individual data of the variant to be represented by a data class (for reuse, the same fields are used across several places with different "tag" discriminators depending on other factors)

@Serializable
data class MyReusedClass(
    val aField: String,
    val anotherField: UInt
)

@Serializable
@JsonClassDiscriminator("tag")
sealed interface MySealedInterface {
    @Serializable
    @SerialName("A")
    data object A : MySealedInterface // Works as you would expect

    @Serializable
    @SerialName("B")
    data class B(val field1: String) : MySealedInterface // Works as you would expect

    @Serializable(with = C.Serializer::class) // Delegating the serializer to `value` doesn't work here, the wrong "tag" is included
    data class C(val value: MyReusedClass) : MySealedInterface {
        object Serializer : KSerializer<C> {
            private val delegateSerializer = MyReusedClass.serializer()
            override val descriptor =
                SerialDescriptor("C", delegateSerializer.descriptor) // The serialName is supposed to be "C"

            override fun serialize(encoder: Encoder, value: C) {
                encoder.encodeSerializableValue(delegateSerializer, value.value)
            }

            override fun deserialize(decoder: Decoder): C {
                return C(decoder.decodeSerializableValue(delegateSerializer))
            }
        }
    }
}

Expected behavior

When serializing MySealedInterface.C(MyReusedClass("someString", 42u)) into JSON, I get
{"tag": "C", "aField": "someString", "anotherField": 42}

Actual behavior

When serializing MySealedInterface.C(MyReusedClass("someString", 42u)) into JSON, I get
{"tag": "my.package.name.MyReusedClass", "aField": "someString", "anotherField": 42}

Environment

  • Kotlin version: 1.9.10
  • Library version: 1.6.0
  • Kotlin platforms: Android
  • Gradle version: 8.0
  • IDE version: Android Studio 2022.3.1 Patch 1
@sandwwraith
Copy link
Member

Yes, it is a bug. It is related to the fact that serial name is written inside a beginStructure call, that happens inside encodeSerializableValue(delegateSerializer), so delegateSerializer.serialName is used instead of the "C". Probably related to #2451 and #2288

@FlorianDenis
Copy link
Author

FlorianDenis commented Mar 12, 2024

Any way to work around it so that I can produce the JSON I am expecting (without manually duplicating the encoding for MyReusedClass) ?

@sandwwraith
Copy link
Member

Perhaps you can workaround it by manually encoding value.value to JsonElement and inserting type in it. See here: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#under-the-hood-experimental

@shanshin
Copy link
Contributor

Fixed in 1.7.0

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