Open wkornewald opened 1 year ago
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
.
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.
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>
.
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.
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? :')
@shalaga44 There are 2 ways that this behaviour can be achieved:
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.
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 isserializersModule
, 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 likeserializersModule
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.