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

Polymorphic default serializer does not affect serialization #1317

Closed
rnett opened this issue Feb 1, 2021 · 9 comments
Closed

Polymorphic default serializer does not affect serialization #1317

rnett opened this issue Feb 1, 2021 · 9 comments

Comments

@rnett
Copy link

rnett commented Feb 1, 2021

Describe the bug

I have an interface Operand, and I'm trying to serialize all Operand instances using a certain serializer. This seems pretty straightforward, I can use a module with polymorphicDefault(Operand::class){ serializer }. However, when I run it, I get:

Exception in thread "main" kotlinx.serialization.SerializationException: Class 'Constant' is not registered for polymorphic serialization in the scope of 'Operand'.
Mark the base class as 'sealed' or register the serializer explicitly.
	at kotlinx.serialization.internal.AbstractPolymorphicSerializerKt.throwSubtypeNotRegistered(AbstractPolymorphicSerializer.kt:103)
	at kotlinx.serialization.internal.AbstractPolymorphicSerializerKt.throwSubtypeNotRegistered(AbstractPolymorphicSerializer.kt:113)
	at kotlinx.serialization.PolymorphicSerializerKt.findPolymorphicSerializer(PolymorphicSerializer.kt:96)
	at kotlinx.serialization.json.internal.PolymorphicKt.findActualSerializer(Polymorphic.kt:29)
	at kotlinx.serialization.json.internal.PolymorphicKt.access$findActualSerializer(Polymorphic.kt:1)
	at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:226)
	at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:71)
	at com.rnett.tf_function.OperandTest$$serializer.serialize(SerializaitonTests.kt:22)

Looking at the code, it seems like this might be because the SerialModuleImpl#getPolymorphic that takes the value never references polyBase2DefaultProvider, only the one that takes the string does.

It may have to do with polymorphicDefault being intended for deserialization only, but I'd like to use it for serialization as well.

To Reproduce

interface Operand{
    val value: String
}
class Constant(override val value: String): Operand

@Serializable
data class OperandTest(val operand: Operand)

class OperandSerializer(): KSerializer<Operand> {
    private val _operands = mutableMapOf<String, KClass<out Operand>>()

    val operands: Map<String, KClass<out Operand>> get() = _operands

    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Operand", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): Operand {
        error("Not supported")
    }

    override fun serialize(encoder: Encoder, value: Operand) {
        _operands[value.value] = value::class
        encoder.encodeString(value.value)
    }
}

inline fun <reified T> getOperands(data: T): Pair<String, Map<String, KClass<out Operand>>> {
    val serializer = OperandSerializer()
    val module = SerializersModule {
        polymorphicDefault(Operand::class){ serializer }
    }
    val template = Json { serializersModule = module }.encodeToString(data)
    return template to serializer.operands
}

fun main() {
    val a = Constant("2")

    val data = OperandTest(a)

    val (template, operands) = getOperands(data)
    println(template)
    println(operands)
}

(I'm aware this is bad practice for serializing stuff, and I don't have access to the real Operand and Constant classes (they are Java classes))

Expected behavior

The OperandSerializer is used for Operand values.

Environment

  • Kotlin version: 1.4.21
  • Library version: 1.0.1
  • Kotlin platforms: JVM
  • Gradle version: 6.8
@rnett
Copy link
Author

rnett commented Feb 1, 2021

This seems similar to #1276 and it's PR, but for polymorphism instead of contextual serialization, and as such I don't think it's fix will work here.

@rnett
Copy link
Author

rnett commented Feb 1, 2021

I'm willing to make a PR for this, if the design is approved. I'd make a second polyBase2DefaultProvider map for SerializationStrategy, and default methods for SerializationStrategy and KSerializer (that adds both serialization and deserialization).

@rnett
Copy link
Author

rnett commented Feb 1, 2021

This should be a feature request for default serializers to apply for serialization, since I just found the docs saying Default serializers provider affects only deserialization process.

@sandwwraith
Copy link
Member

Yes, Default serializers provider affects only deserialization process is that how things are intended to be. So, if I get you right, you want a default serializer to affect serialization, too? In that case, maybe you just need not to use polymorphism and always serialize your base class with your custom serializer (i.e. simply use class OperandTest(@Serializable(OperandSerializer::class) val operand: Operand)). Another way to work this around is to register all known subclasses in polymorphic module and assign your custom serializer to them

@sandwwraith sandwwraith changed the title Polymorphic default serializer does not seem to be found Polymorphic default serializer does not affect serialization Feb 12, 2021
@rnett
Copy link
Author

rnett commented Feb 12, 2021

So, if I get you right, you want a default serializer to affect serialization, too?
Yeah

I'm working with Java APIs so I can't register subclasses without classpath scanning or other trickery. If I have Operand properties, I can use @Contextual just fine, like in your example, but I would like to be able to handle Constant properties too, and @Serializable(OperandSerializer::class) doesn't work since I want to use the same serializer instance (besides the types not working).

@CrushedPixel
Copy link

+1 on this issue, I am trying to serialize a Java interface that has only one implementation class. Since that class is private, I can't explicitly register a serializer for it. I'd prefer not to annotate everything with @Serializable(with = MyInterfaceSerializer::class), so having polymorphicDefault also apply to serialization is very needed.

@vnermolaev
Copy link

+1 on this issue. To add another perspective on this issue: in my case, there are many implementations of an interface for which I can easily derive a serializer, but registering all subclasses is not only tedious but also brittle.

@qwwdfsad
Copy link
Member

Thanks for the updates on use-cases, we'll revisit this issue

@sandwwraith
Copy link
Member

Fixed by #1686

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

5 participants