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

Customizing serialization (without @Contextual) #2279

Closed
wkornewald opened this issue Apr 18, 2023 · 7 comments
Closed

Customizing serialization (without @Contextual) #2279

wkornewald opened this issue Apr 18, 2023 · 7 comments
Labels
question Static modules Request for serializer that can be added to Java module

Comments

@wkornewald
Copy link

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

We have lots of modules which have two or three different ZonedDateTime encodings because different backends have different format requirements. How can we avoid plastering the whole code with @Contextual and getting bugs where we forget to add the annotation? (esp. if ZonedDateTime has a default serializer)

Another use-case is that we have individual encrypted fields and we'd like to e.g. set an @Encrypted annotation on them so that the value is pre/post-processed for (de-)serialization.

Describe the solution you'd like

We'd like to have one central place to set the serializer per Json instance. There already is serializersModule, but it's not clear to us why we additionally have to set @Contextual everywhere in the code (or per file). This feels completely unnecessary and just makes the code more error-prone. Is it possible to have something like serializersModule without @Contextual somehow?

Ideally, we'd like to be able to inject some mapper function which gets the type and information about the field (so we can lookup potential annotations) and then it can override the default (de-)serialization behavior either fully or pre/post-process the data before/after the default serialization behavior.

@sandwwraith
Copy link
Member

sandwwraith commented Apr 18, 2023

kotlinx.serialization uses a compiler plugin, which means that actual serializer for type gets compiled into the generated serialization code. There is no type mapping for it on runtime, as KClass is never retrieved. That's why @Contextual is needed — a special serializer that captures KClass and can perform a lookup in serializersModule.

To ease specifying serializers, I can recommend you one of the techniques from here: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#specifying-serializers-for-a-file
See 'Specifying serializers for a file' or 'Specifying serializer globally using typealias'. There is also @file:UseContextualSerialization.

@wkornewald
Copy link
Author

wkornewald commented Apr 18, 2023

The @file:UseContextualSerialization solution is something we wanted to avoid because if you have tons of files it's too easy to forget.

Also, the compiler plugin could simply treat all non-primitive types as if they had @Contextual (unless there's an explicit serializer defined for that field). Then we'd never need to do this annotation by hand. What would be the problem with this solution?

Alternatively, what about having a simple interface like this

interface SerializationMapper {
    fun serialize(value: Any?, defaultSerialization: SerializationMapper, fieldAnnotations: List<Any>): Any?
}

The default behavior would be contained in the defaultSerialization which you can just delegate to, but you can also return any transformed value. This way we could just do

class ZonedDateTimeMapper(val someFormatSpec: IsoFormatSpec) : SerializationMapper {
    fun serialize(value: Any?, defaultSerialization: SerializationMapper, fieldAnnotations: List<Any>): Any? =
        if (value is ZonedDateTime)
            value.formatToIsoString(someFormatSpec)
        else
            defaultSerialization.serialize(value, defaultSerialization, fieldAnnotations)
}

Though, just having a default @Contextual annotation applied to all fields automatically might be sufficient.

@sandwwraith
Copy link
Member

sandwwraith commented Apr 19, 2023

Also, the compiler plugin could simply treat all non-primitive types as if they had @contextual (unless there's an explicit serializer defined for that field). Then we'd never need to do this annotation by hand. What would be the problem with this solution?

This goes against design principles: given that classes are processed at compile time, we can also report errors about missing serializers at compile time and in the IDE, which is great and important for us. By auto-inserting @Contextual everywhere instead, we lose this ability, and debugging runtime errors about missing serializers usually takes more time. Also, it slows down the serialization process, as every non-primitive property would require lookup in the Map<KClass, KSerializer>.

@sandwwraith sandwwraith added the Static modules Request for serializer that can be added to Java module label Apr 19, 2023
@wkornewald
Copy link
Author

wkornewald commented Apr 19, 2023

Then how about introducing module-wide serializer mappings and also adding Contextual for types which define a serializer (e.g. so kotlinx-datetime's Instant can be customized without extra annotations)?

I'd rather take a small performance hit than deal with bugs due to missing Contextual. But module wide rules could even solve the performance issue.

@shalaga44
Copy link

Still can't find a way to make the plugin —in enums— use the explicit serializer if it exist, and the default serializer if not without using @Contextual.

my use-case is I have a database persistenceName (maintained by JetBrains/Exposed Custom Column) & dtoName that had to be maintained differently:

enum class ExampleEnum(
    override val dtoName: String,
    override val persistenceName: String
) : EnumDtoName, EnumPersistenceName {
    FIRST(dtoName = "first_on_mobile", persistenceName = "first_on_database"),
    SECOND(dtoName = "second_on_mobile", persistenceName = "second_on_database"),
    THIRD(dtoName = "third_on_mobile", persistenceName = "third_on_database")
}

Any update on this? :')

@pdvrieze
Copy link
Contributor

@shalaga44 There are 2 ways that this behaviour can be achieved:

  • Using a custom serializer that has some (global) configuration that decides how to serialize given that configuration.
  • Use a custom format that delegates to Json for its actual behaviour (don't forget to wrap returned encoders/decoders), and can replace serializers with different ones (as far as the Json format is concerned the original serializer is invisible, but be careful with serialDescriptors that need to be correct too). The advantage is that the configuration can be for the delegate format, not globally.

@sandwwraith
Copy link
Member

Related discussion: #507

@sandwwraith sandwwraith closed this as not planned Won't fix, can't repro, duplicate, stale Nov 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Static modules Request for serializer that can be added to Java module
Projects
None yet
Development

No branches or pull requests

4 participants