question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Allow custom policy for adding the polymorphic discriminator

See original GitHub issue

What is your use-case and why do you need this feature?

There has been several use cases mentioned in different issues that require to add the discriminator in more places, or avoid it in some places:

  • for serialization-only use cases (such as sending data to an external API), we may not need discriminators at all. They bloat the payload at best, and can actually fail the requests at worst (https://github.com/Kotlin/kotlinx.serialization/issues/464)
  • for interop with other deserialization libraries, possibly in more dynamic languages, we may want to have a discriminator for every type (https://github.com/Kotlin/kotlinx.serialization/issues/1128)
  • even when using only Kotlinx Serialization itself for both serialization and deserialization, there are times when the deserializing party has less information than the serializing party about the type in the JSON, and we may want to consistently include the discriminator for subtypes of polymorphic types that are serializable (https://github.com/Kotlin/kotlinx.serialization/issues/1194). An example of that is sending events over a “pipe” (websocket or other). We know the concrete type at serialization time, so the subtype’s serializer can be used, but the deserializer at the other end expects any type of event, so it uses the parent type’s deserializer. Currently the discriminator is never added by the child type’s serializer, so the parent deserializer fails in this case. It is not always possible to control how kotlinx.serialization is called at the place we send the events, so using the parent type’s serializer when serializing the value is not always an option (for instance, some frameworks like Spring look for the serializer to use based on the dynamic value’s type, and thus never use the polymorphic parent’s serializer).

Describe the solution you’d like

It seems these use cases could be supported by having a configuration parameter for the inclusion of the discriminator, such as discriminatorInclusionPolicy, with the following values:

  • NEVER: never include the discriminator
  • POLYMORPHIC_TYPES (current behaviour): include the discriminator only when serializing a type that’s polymorphic itself (the “parent” type).
  • POLYMORPHIC_SUBTYPES: always include the discriminator for instances (subtypes) of polymorphic types that are serializable, regardless of whether they are used as their parent or as themselves (json.encodeToString<Parent>(SubType()) and json.encodeToString<SubType>(SubType()) would behave the same way).
  • ALWAYS, ALL_TYPES, or ALL_OBJECT_TYPES: always include the discriminator in types serialized as JSON objects (does not apply to primitive types)

Maybe the names could be refined, but I think you get the idea.

Another option could also be to provide a custom function that tells whether to include the discriminator or not, but then we would have to think about which pieces of information the function would need etc.

Any thoughts on this?

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:38
  • Comments:18 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
pdvriezecommented, Jun 1, 2021

This is an implementation detail: the discriminator is added by the format, not by the serializer. So we need to keep a ‘default’ serializer for child and do not use PolymorphicSerializer for it because this will lead to a stack overflow

Just to clarify for the others, polymorphic (de)serialization, the (outer) polymorphic serializer that has 2 elements (the type discriminator, and the data element). The data element is then (de)serialized using the type specific serializer. The format (json when not in array format, xml in transparent mode) can elide this extra structure and use a different approach instead (type discriminator, type specific tag name).

Basically it works as follows:

  • A polymorphic serializer tells the format that polymorphic serialization is used, and it provides the base type, type discriminator, child serializer and child data.
  • The format decides how to handle this information in a format specific way

As such, without having a polymorphic serializer a format does not know how a type is used, or even what the base type is (there could be multiple valid parent types for the same polymorphic leaf type).

For sealed serialization, there is only one serializer generated for child, this is the type specific (non-polymorphic) serializer. Then for any sealed parent (there could be multiple, hierarchic), this is always abstract and the descriptor contains the information of all the possible sub-serializers in the sealed hierarchy.

The child adding a discriminator on its own is hard (and certainly doesn’t fit the current architecture). The problem is that the format is then unable to know that the (synthetic) property is actually a type discriminator. More significant however is that the serializer itself cannot know the context of it’s own use (polymorphic or static).

It is perfectly possible for a format to add a discriminator for all structure types, it is also possible for it to create a set of typenames based upon the content of a SerializersModule (using the dumpTo function). This has performance drawbacks. As an alternative, it is also possible to “wrap” the serializers into remapping serializers that wrap all desired classes into a polymorphic/sealed wrapper. If you only care about the outer type, then it is even easier, just put it in a polymorphic (or sealed) parent when creating the serializer.

1reaction
sandwwraithcommented, May 26, 2021

As for now, there is sufficient demand and use-cases to provide an option to globally disable type discriminator (NEVER option) — mainly, to interact with external API that has validation or just to reduce the amount of data. However, to correctly design and implement this feature we need to decide whether to provide other options besides NEVER.

POLYMORPHIC_SUBTYPES, as in the proposal, is a questionable strategy because it makes rules harder to understand — besides static property type, we also need to check if the class is registered in the current module, etc, etc. Instead, we suggest providing a diagnostic when the user probably wants to use base polymorphic type instead of subtype: #1493

ALWAYS also seems vague and rather exotic use-case: you don’t really want an external API to depend on your implementation details, such as class serial names. Good implementation with backward compatibility of such a pattern requires a manual providing a @SerialName for each serializable class of your program. Moreover, the current implementation of polymorphism does not allow to specify serial names and discriminators for primitives and enums; implementing ALWAYS strategy would be an inappropriate cheat-code to overcome this limitation — because, well, not all classes would have discriminators, despite ALWAYS state.

So for now we are leaning towards a design with only two states: DEFAULT (current behavior) and NEVER (do not include discriminator at all). If you have any additional suggestions or corrections, please add them

Read more comments on GitHub >

github_iconTop Results From Across the Web

Polymorphic serializer was not found for missing class ...
Base class for custom serializers that allows selecting polymorphic serializer without a dedicated class discriminator, on a content basis.
Read more >
7 Defining Polymorphic View Objects - Oracle Help Center
To identify the row type, the polymorphic view object relies on a discriminator attribute to identify each row's corresponding table.
Read more >
JsonClassDiscriminator - Kotlin
Specifies key for class discriminator value used during polymorphic serialization in Json. Provided key is used only for an annotated class and its ......
Read more >
OpenAPI Specification v3.1.0 | Introduction, Definitions, & More
To support polymorphism, the OpenAPI Specification adds the discriminator field. When used, the discriminator will be the name of the property ...
Read more >
Hibernate Inheritance Mapping - Baeldung
Let's start by creating a Person class that will represent a parent class: ... To customize the discriminator column, we can use the ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found