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

"Serializer for subclass not found" for nested sealed class with custom serializer #2625

Open
PatrickHum-at opened this issue Apr 11, 2024 · 1 comment

Comments

@PatrickHum-at
Copy link

Describe the bug

I'm running into an issue where I need to use a JsonContentPolymorphicSerializer on a nested sealed class. The children of the nested sealed class share the same top level discriminator, and are differentiated by their content.

To Reproduce

@Serializable
sealed class Base {
    @Serializable
    @SerialName("a")
    data class A(val value: Int) : Base()

    @Serializable(with = InnerBase.Companion::class)
    @SerialName("b")
    sealed class InnerBase : Base() {
        @Serializable
        data class D(val value: Int) : InnerBase()

        @Serializable
        data class E(val value: Int) : InnerBase()

        companion object : JsonContentPolymorphicSerializer<InnerBase>(InnerBase::class) {
            override fun selectDeserializer(element: JsonElement): DeserializationStrategy<InnerBase> {
                return if ("foo" in element.jsonObject) {
                    D.serializer()
                } else {
                    E.serializer()
                }
            }
        }
    }
}

class NestedCustomSerializerTest {
    val json = Json { }

    @Test
    fun `test deserialize D`() {
        val actual = json.decodeFromString<Base>(
            """{
            "type": "b",
            "foo": 1,
            "value": 2
            }""".trimMargin()
        )
        assertEquals(Base.InnerBase.D(2), actual)
    }

    @Test
    fun `test deserialize A`() {
        val actual = json.decodeFromString<Base>(
            """{
            "type": "a",
            "value": 7
            }""".trimMargin()
        )
        assertEquals(Base.A(7), actual)
    }
}

When running test deserialize D I get the following error:

kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'b' is not found in the polymorphic scope of 'Base' at path: $
Check if class with serial name 'b' exists and serializer is registered in a corresponding SerializersModule.
To be registered automatically, class 'b' has to be '@Serializable', and the base class 'Base' has to be sealed and '@Serializable'.
JSON input: {
            "type": "b",
            "foo": 1,
            "value": 2
            }
	at app//kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
	at app//kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:32)
	at app//kotlinx.serialization.json.internal.AbstractJsonLexer.fail(AbstractJsonLexer.kt:598)
	at app//kotlinx.serialization.json.internal.AbstractJsonLexer.fail$default(AbstractJsonLexer.kt:596)
	at app//kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:85)
	at app//kotlinx.serialization.json.Json.decodeFromString(Json.kt:107)

Expected behavior

The JSON is deserialized to Base.InnerBase.D(2).

Environment

  • Kotlin version: 1.9.23
  • Library version: 1.6.3
  • Kotlin platforms: JVM (Android)
  • Gradle version: 8.6
@PatrickHum-at
Copy link
Author

PatrickHum-at commented Apr 11, 2024

We did find a workaround using SerializersModule but the default behavior seems odd.

 polymorphicDefaultDeserializer(Base::class) { className ->
        when (className) {
            "b" -> Base.InnerBase.Companion
            else -> error("Unknown type $className")
        }
    }

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

No branches or pull requests

1 participant