-
Notifications
You must be signed in to change notification settings - Fork 628
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
Combining SealedClassSerializer
s
#1865
Comments
So basically you want something like that, right?
for combining two sealed hierarchies. It sounds quite reasonable since we have an API to combine |
Ideally one wouldn't have to care whether the polymorphic subclass is sealed or not, and the runtime would add all concrete subclasses of the subtype without needing to combine modules. At least I can't think of a use case where one would not want this to happen automatically. For example, I added a // HACK: We can't access the subclass serializers in SealedClassSerializer because
// they are stored in a private "class2Serializer" field. This exposes the field
// via reflection.
// Solution taken from: https://github.com/Kotlin/kotlinx.serialization/issues/1865
@OptIn(InternalSerializationApi::class)
internal actual object SealedClassSerializerPrivate {
private val class2SerializersField = SealedClassSerializer::class.java
.getDeclaredField("class2Serializer")
.apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
actual fun <T : Any> class2Serializer(
serializer: SealedClassSerializer<T>
): Map<KClass<out T>, KSerializer<out T>> =
class2SerializersField.get(serializer) as Map<KClass<out T>, KSerializer<out T>>
}
@OptIn(InternalSerializationApi::class)
private fun <T : Any> findSubclassSerializers(
subclass: KClass<out T>,
serializer: KSerializer<out T>
): Map<KClass<out T>, KSerializer<out T>> = when (serializer) {
is SealedClassSerializer<out T> -> SealedClassSerializerPrivate.class2Serializer(serializer)
.flatMap { (subclass_, serializer_) ->
findSubclassSerializers(subclass_, serializer_).toList()
}
.toMap()
else -> mapOf(subclass to serializer)
}
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
@PublishedApi
internal inline fun <T> KSerializer<*>.cast(): KSerializer<T> = this as KSerializer<T>
fun <Base : Any, T : Base> PolymorphicModuleBuilder<Base>.subclassesOf(
subclass: KClass<T>,
serializer: KSerializer<T>
) {
for ((subclass_, serializer_) in findSubclassSerializers(subclass, serializer)) {
subclass(subclass_, serializer_.cast())
}
}
inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder<Base>.subclassesOf(
serializer: KSerializer<T>
): Unit = subclassesOf(T::class, serializer)
inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder<Base>.subclassesOf(
clazz: KClass<T>
): Unit = subclassesOf(clazz, serializer()) And now one can register a sealed type as a subclass as long as it is serializable itself, as the following test demonstrates: interface BaseInterface
@Serializable
sealed interface SealedInterface : BaseInterface {
@Serializable
object Object : SealedInterface
}
val json = Json {
serializersModule = SerializersModule {
polymorphic(BaseInterface::class) {
subclassesOf(SealedInterface::class)
}
}
}
val encoded = json.encodeToString<BaseInterface>(SealedInterface.Object)
// -> {"type":"SealedInterface.Object"}
json.decodeFromString<BaseInterface>(encoded)
// -> SealedInterface.Object |
Looking at the fact that it is/should be possible to have custom serializers of |
This API has been around for a long time and has proven itself useful. The main reason @ExperimentalSerializationApi was on SerialDescriptor's properties is that we wanted to discourage people from subclassing it. With the introduction of @SubclassOptInRequired (#2366), we can do this without the need of marking everything with experimental. Serial kinds fall into the same category with only exception in PolymorphicKind. There are plenty requests for functionality like creating a custom sealed-like descriptor (#2697, #2721, #1865) which may require additional kinds in the future.
This API has been around for a long time and has proven itself useful. The main reason @ExperimentalSerializationApi was on SerialDescriptor's properties is that we wanted to discourage people from subclassing it. With the introduction of @SubclassOptInRequired (#2366), we can do this without the need of marking everything with experimental. Serial kinds fall into the same category with only exception in PolymorphicKind. There are plenty requests for functionality like creating a custom sealed-like descriptor (#2697, #2721, #1865) which may require additional kinds in the future.
This API has been around for a long time and has proven itself useful. The main reason @ExperimentalSerializationApi was on SerialDescriptor's properties is that we wanted to discourage people from subclassing it. With the introduction of @SubclassOptInRequired (#2366), we can do this without the need of marking everything with experimental. Serial kinds fall into the same category with only exception in PolymorphicKind. There are plenty requests for functionality like creating a custom sealed-like descriptor (#2697, #2721, #1865) which may require additional kinds in the future.
This API has been around for a long time and has proven itself useful. The main reason @ExperimentalSerializationApi was on SerialDescriptor's properties is that we wanted to discourage people from subclassing it. With the introduction of @SubclassOptInRequired (#2366), we can do this without the need of marking everything with experimental. Serial kinds fall into the same category with only exception in PolymorphicKind. There are plenty requests for functionality like creating a custom sealed-like descriptor (#2697, #2721, #1865) which may require additional kinds in the future.
This API has been around for a long time and has proven itself useful. The main reason @ExperimentalSerializationApi was on SerialDescriptor's properties is that we wanted to discourage people from subclassing it. With the introduction of @SubclassOptInRequired (#2366), we can do this without the need of marking everything with experimental. Serial kinds fall into the same category with only exception in PolymorphicKind. There are plenty requests for functionality like creating a custom sealed-like descriptor (#2697, #2721, #1865) which may require additional kinds in the future.
This API has been around for a long time and has proven itself useful. The main reason @ExperimentalSerializationApi was on SerialDescriptor's properties is that we wanted to discourage people from subclassing it. With the introduction of @SubclassOptInRequired (#2366), we can do this without the need of marking everything with experimental. Serial kinds fall into the same category with only exception in PolymorphicKind. There are plenty requests for functionality like creating a custom sealed-like descriptor (#2697, #2721, #1865) which may require additional kinds in the future.
What is your use-case and why do you need this feature?
I have a base, non-sealed, interface, from which sealed classes extend. At runtime (this is a generic framework), I need to be able to create a serializer which can handle all subclasses of multiple sealed classes that extend from this common interface.
My initial expectation was that I could instantiate a new
SealedClassSerializer
and pass allsubclasses
andsubclassSerializers
from the other serializers as constructor parameters. However, I found out that after instantiatingSealedClassSerializer
this information is no longer accessible (it is stored in a privateclass2Serializer
field).As an example, my specific use case: I have an
IntegrationEvent
interface and application services which each define the events they emit by defining a sealed class which extends from this interface. Application services can subscribe to events from other services. Serializing any event received from dependent services thus requires me to be able to deserialize any of the events defined as subclasses for each of the sealed event classes.P.s. I understand
SealedClassSerializer
is an internal serialization API; I find myself accessing internal APIs quite frequently since I'm usingkotlinx.serialization
in quite elaborate use cases. I hope sharing these may inspire some of the internal APIs to be stabilized and made public, since I would argue these are valid use cases. For now, I don't mind using them as unstable APIs.Describe the solution you'd like
Could
subclasses
andsubclassSerializers
, or a meaningful map thereof, be made public as readonly fields? Once you have access to this class, I don't think information hiding makes sense. In addition, maybe a factory method can be added which takes multiple other sealed class serializers to combine them.I currently hack this using JVM reflection as workaround:
The text was updated successfully, but these errors were encountered: