-
Notifications
You must be signed in to change notification settings - Fork 629
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
Explicit polymorphism discriminator #2903
Comments
Hey, KtMongo author here. I'll give a bit more context. KtMongo relies on KotlinX.Serialization to convert Kotlin types to and from MongoDB BSON. Through this, we support KtMongo provides an extensive DSL over MongoDB operators. This DSL is implemented through extension functions and generics, allowing us to refer to fields when writing requests: @Serializable
class User(
val name: String,
val age: Int,
)
users.find {
User::age gt 18
} So far, so good. The problem comes because of the imaginary field KotlinX.Serialization creates on sealed types: @Serializable
sealed class Foo
@Serializable
class FooA(val a: Int) : Foo()
@Serializable
class FooB(val b: String) : Foo() A instance of {
"type": "FooB",
"b": "foo"
} We would like to be able to write a query for specifically instances of users.find {
User::foo / Foo::type eq "foo" // ⚠ Foo::type does not exist
} This could be worked around by creating an extension function and hard-coding the name By using explicit enums, this issue removes the magic around the discriminant, so it can be accessed through regular Kotlin code. |
@Tmpod The information you are looking for is where @CLOVIS-AI You can get this information as text from the descriptor: |
@CLOVIS-AI Thank you for adding more context!
What I'm proposing would avoid all that hardcoding. By explicitly marking a field as the discriminant, you remove the unintuitive behaviour (imo) of virtual fields. The serialization library knows which field it should encode/decode first and how to map values to serializers. I just don't know how feasible it is to get the discriminant value from each implementor in the context of an annotation processor or compiler plugin. To me, it should be possible, the value is static, but I really am not familiar with the capabilities exposed to processors/plugins (though I've already noticed limitations, such as annotations being incapable of taking values I'd consider compile-time constants, such as enum variants).
I'm not sure I follow. I used |
I guess what you want here is #1664, so it's a duplicate. I also would like to point out that kotlinx.serialization by default always performs polymorphic serialization in array format: |
Do you mean you want to read the value of the type without using runtime type information (using the fact that the instance is of the specific type). As to what "field" is used to decide how to deserialize, the name is always the same (except when explicitly overridden). In the specific case of Json this can be provided either globally in the format configuration, or per type (not attribute) using As to "not having to resort to @Serializable
abstract class Base {
abstract val type: String
}
@Serializable
class Child(val myProp:String) {
override val type: String get() = Child.serializer().descriptor.serialName // or a hardcoded/cached version
} |
I suppose it is related, yes, but what I was suggesting was more versatile, as I see it. The referenced issue is also in part about nested hierarchies and
I had read about that, but I'm not grasping the reasoning behind why kx.ser doesn't support field-based discriminants at the core library level. Why does it need to be a format-specific thing? Either way, an annotation that could tell the format implementors which field should be used for discriminating would be nice, instead of having each format re-implement the same annotations (kbson has a similar |
Can you clarify what the value will return? Also, that is a bit verbose to add to each class. Could there be a way to automatically generate this somehow? |
Yeah. Since that field exists in the serialized document, why not have it in the deserialized object too? Makes some things easier, and in the case of KMongo/KtMongo's and similar DSLs, it's important to have it.
I am aware of that annotation, however it's format specific (and string-based, but that's a minor nitpick and a limitation of annotations). I think an annotation on the field itself would be clearer to developers — this field will be used to resolve polymorphic serializers; no virtual fields at play, no using Again, not entirely sure if this is truly implementable, but perhaps this could work: generate and expose a getter for the discriminant field (its SerialDescriptor, maybe?), that the implementing formats and serializers could use to know which field to encode first and how to match the first decoded value to select the deserializer. It sounds to me like it could even be implemented a separate thing, though it would be more useful to have that built into the core library itself. If this sounds plausible, I could try implement it to help illustrate what I mean by all this 😅 |
What is your use-case and why do you need this feature?
I've been experimenting with MongoDB through KMongo and KtMongo, specifically around polymorphic documents. Both kbson (used by KMongo) and the official Kotlin Mongo driver (used by KtMongo) support polymorphic serialization, but always through a virtual field (whose name is dependent on the format).
This isn't ideal. You have to resort to clunky workarounds to use the type-safe query API, you have use
SerialName
everywhere and in general it isn't intuitive at all. Implicit fields make code harder to reason about, and often require diving into library sources to really understand what's happening.Here's an example with current implicit fields
Describe the solution you'd like
Let's consider a new
@PolymorphicDiscriminator
annotation that tells kx.ser and implementing formats which field should be used for polymorphic (de)serialization. One could write the following instead:The same example but using this suggested annotation
I'm not well versed in compiler plugins or annotation processors, so I'm not entirely sure on the implementation details, but all the semantic information required for such an annotation exist. When you're processing a class that is part of a sealed hierarchy, you check if the value it defines is always known at compile-time and if it is unique (e.g. you can't have two implementors use the same discriminator value). I'd even say the value could be anything directly matchable in
when
expressions.If the values are all consistent, you can generate the polymorphic deserialization function which selects the correct implementor serializer based on the decoded discriminator, akin to what already happens on the current implementation based on an implicit field.
Please correct me if anything I've said is wrong or if my suggestion is unattainable at the moment. Thank you!
The text was updated successfully, but these errors were encountered: