# Closed polymorphism with kotlinx.serialization (and KMongo)

**URL:** <https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684>\
**Category:** Support\
**Created:** [December 30, 2024, 4:56pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684 "2024-12-30T16:56:39Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![tmpod](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.kotlinlang.org/tmpod/32/9480_2.png) [@tmpod](https://discuss.kotlinlang.org/u/tmpod)\
**Post date:** [December 30, 2024, 4:56pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/1 "2024-12-30T16:56:39Z")

</div>

Hello!

I’ve been developing something with MongoDB, where I store store documents which share a a set of common fields and then can be one of a closed set of variants, each with its own set of specific fields. While quickly prototyping, I just had a single data class with all variant-specific fields nullable and had a `type` discriminant field + a simple enum of all variants. Very hacky, I know.

Now that I’m starting to work on writing this properly, my first thought was to model this with sealed classes. Sometimes I need to filter for specific types of documents, and since it was also useful in many other places, I’d like to keep the `type` field in my sealed class. This creates a duplication of fields, however, since kx.ser adds a `type` field (or `___type` in case of a name clash, as is the case here) with the class FQN. I’d like to avoid that completely and just use the enum (serialized as its name string).

Is there any way to achieve this? If not, what do you suggest I do instead?

Thank you in advance 😃

---

<div class="post-metadata">

**Author:** ![madmax1028](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.kotlinlang.org/madmax1028/32/3335_2.png) [@madmax1028](https://discuss.kotlinlang.org/u/madmax1028)\
**Post date:** [December 30, 2024, 5:20pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/2 "2024-12-30T17:20:05Z")

</div>

Is there really any need for that? If you have `sealed` classes you can simply filter objects by using `.filterIsInstance()` or `when()`.

---

<div class="post-metadata">

**Author:** ![madmax1028](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.kotlinlang.org/madmax1028/32/3335_2.png) [@madmax1028](https://discuss.kotlinlang.org/u/madmax1028)\
**Post date:** [December 30, 2024, 5:38pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/3 "2024-12-30T17:38:01Z")

</div>

If you are set on having enum as a field you could do it this way:

```auto
enum class EnumType {
	TYPE_A, TYPE_B
}

@Serializable
sealed interface SealedType {
	val type: EnumType
}

@Serializable
@SerialName("TYPE_A")
data class SealedTypeA(val intProperty: Int) : SealedType {
	override val type get() = EnumType.TYPE_A
}

@Serializable
@SerialName("TYPE_B")
data class SealedTypeB(val stringProperty: String) : SealedType {
	override val type get() = EnumType.TYPE_B
}

```

---

<div class="post-metadata">

**Author:** ![tmpod](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.kotlinlang.org/tmpod/32/9480_2.png) [@tmpod](https://discuss.kotlinlang.org/u/tmpod)\
**Post date:** [December 30, 2024, 6:05pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/4 "2024-12-30T18:05:37Z")

</div>

> [@madmax1028](#):
>
> Is there really any need for that? If you have `sealed` classes you can simply filter objects by using `.filterIsInstance()` or `when()`.

While that technically could work, you don’t ever want to do that on the Kotlin side, it should be done as a database filter operation. In case of KMongo, you can do stuff like `Foo::type eq ...` to construct a database filter.

Another technically possible option would be to split the documents into separate collections, but that’s not actually feasible either, because I also need to query based on the common type, and that would imply querying several different collections and somehow join them. Not possible.

> [@madmax1028](#):
>
> If you are set on having enum as a field you could do it this way:

That is what I have right now, but it duplicates the type discriminator, since it serializes `SealedType::type` field and then kx.ser’s polymorphism type (which gets renamed to `___type` to avoid clashing). I end up with a serialization like this (not necessarily JSON):

```json
{"type": "TYPE_A", "___type": "TYPE_A", "intProperty": 123}

```

My question is if I can make kx.ser just use `SealedType::type` and avoid adding `___type`.

---

<div class="post-metadata">

**Author:** ![madmax1028](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.kotlinlang.org/madmax1028/32/3335_2.png) [@madmax1028](https://discuss.kotlinlang.org/u/madmax1028)\
**Post date:** [December 30, 2024, 6:14pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/5 "2024-12-30T18:14:37Z")

</div>

Not exactly. The example I provided doesn’t generate a duplicate `type` field. That is because there are no backing fields when using `get()` construct.

---

<div class="post-metadata">

**Author:** ![tmpod](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.kotlinlang.org/tmpod/32/9480_2.png) [@tmpod](https://discuss.kotlinlang.org/u/tmpod)\
**Post date:** [December 30, 2024, 6:30pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/6 "2024-12-30T18:30:07Z")

</div>

Ah I see. Will have to try it out to see if it plays well with KMongo’s filtering API. I also noticed you used `sealed interface`, which means it wouldn’t be possible to deserialize to `SealedType` (for when I’m working with common fields only), correct?

---

<div class="post-metadata">

**Author:** ![tmpod](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.kotlinlang.org/tmpod/32/9480_2.png) [@tmpod](https://discuss.kotlinlang.org/u/tmpod)\
**Post date:** [December 30, 2024, 6:38pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/7 "2024-12-30T18:38:08Z")

</div>

Alright, this seems to work well (with a sealed class in my case), although kx.ser still adds those prefixing underscores (possible because it sees the `type` field). Is there any way to tell it to just name it `type`?

Thanks a lot for the help! :3

---

<div class="post-metadata">

**Author:** ![madmax1028](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.kotlinlang.org/madmax1028/32/3335_2.png) [@madmax1028](https://discuss.kotlinlang.org/u/madmax1028)\
**Post date:** [December 31, 2024, 1:48pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/8 "2024-12-31T13:48:47Z")

</div>

Here is an alternative way to implement this. Although it just further proofs for me that this is just a combersome idea. 🙂

```kotlin
enum class EnumType {
	TYPE_A, TYPE_B
}

@Serializable(SealedTypeSerializer::class)
sealed interface SealedType {
	val type: EnumType
}

object SealedTypeSerializer : JsonContentPolymorphicSerializer<SealedType>(SealedType::class) {
	override fun selectDeserializer(element: JsonElement) =
		when (enumValueOf<EnumType>(element.jsonObject.getValue("type").jsonPrimitive.content)) {
			EnumType.TYPE_A -> SealedTypeA.serializer()
			EnumType.TYPE_B -> SealedTypeB.serializer()
		}
}

@Serializable
data class SealedTypeA(val intProperty: Int) : SealedType {
	@EncodeDefault
	override val type = EnumType.TYPE_A
}

@Serializable
data class SealedTypeB(val stringProperty: String) : SealedType {
	@EncodeDefault
	override val type = EnumType.TYPE_B
}

```

---

<div class="post-metadata">

**Author:** ![tmpod](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.kotlinlang.org/tmpod/32/9480_2.png) [@tmpod](https://discuss.kotlinlang.org/u/tmpod)\
**Post date:** [January 1, 2025, 9:12pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/9 "2025-01-01T21:12:14Z")

</div>

Mmm, that’s a bit more contrived, for sure. What do you suggest, then? I want to have the sealed hierarchy for better type safety in the code, but I also kinda need that `type` field to filter documents with KMongo.

---

<div class="post-metadata">

**Author:** ![clovisai](https://avatars.discourse-cdn.com/v4/letter/c/a9adbd/32.png) [@clovisai](https://discuss.kotlinlang.org/u/clovisai)\
**Post date:** [January 6, 2025, 11:02pm UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/10 "2025-01-06T23:02:32Z")

</div>

Hey @madmax1028! I’m the author of KtMongo, a new driver for MongoDB. I’m very interested in upstreaming this solution, but I’m wondering if it can be reduced further.

For example, an easy footgun in this implementation is that the user writing a subclass must declare `type = <the correct enum subtype>`. Let’s try to fix that.

What if KtMongo provided an interface:

```kotlin
interface MongoClosedPolymorphism<out E : Enum<E>> {
    val type: E
}

fun <E : Enum<E>> MongoClosedPolymorphism(e: E): MongoClosedPolymorphism<E> =
    object : MongoClosedPolymorphism<E> {
        override val type: E get() = e
    }

// and the serializer that's convenient

```

This way, a user of the library would have to write:

```kotlin
enum class EnumType {
	TYPE_A, TYPE_B
}

@Serializable(MongoClosedPolymorphismSerializer::class)
sealed class SealedType : MongoClosedPolymorphism<EnumType> {
    // …
}

@Serializable
data class SealedTypeA(
    val intProperty: Int,
) : SealedType, MongoClosedPolymorphism<EnumType> by MongoClosedPolymorphism<EnumType.A>

@Serializable
data class SealedTypeB(
    val intProperty: Int,
) : SealedType, MongoClosedPolymorphism<EnumType> by MongoClosedPolymorphism<EnumType.B>

```

That seems a bit better, but I’m sure it can be improved further 🤔

The main two problems left, I think, are:

- It’s still quite verbose
- If you forget to annotate the sealed type with `@Serializable(…Serializer::class)` then it won’t work

---

<div class="post-metadata">

**Author:** ![cabman](https://avatars.discourse-cdn.com/v4/letter/c/50afbb/32.png) [@cabman](https://discuss.kotlinlang.org/u/cabman)\
**Post date:** [January 25, 2025, 5:05am UTC](https://discuss.kotlinlang.org/t/closed-polymorphism-with-kotlinx-serialization-and-kmongo/29684/11 "2025-01-25T05:05:56Z")

</div>

Would it work like this:

```auto
enum class EnumType {
	TYPE_A, TYPE_B
}

@Serializable
sealed class SealedType(val type: EnumType)

@Serializable
@SerialName("TYPE_A")
data class SealedTypeA(val intProperty: Int)
  : SealedType(EnumType.TYPE_A) {
	
}

@Serializable
@SerialName("TYPE_B")
data class SealedTypeB(val stringProperty: String)
  : SealedType(EnumType.TYPE_B) {
	
}

```
