kotlinx.serialization
kotlinx.serialization copied to clipboard
Global custom serializers
What is your use-case and why do you need this feature?
If you have a data class with many "unsupported" types, but very common types in the JDK it is unweidly to use @ContextualSerialization everywhere - either on a file basic or a property basis.
For example,
data class Foo(val decimal: BigDecimal, val id: UUID, val date: LocalDateTime)
Three types, needing three @ContextualSerialization.
Describe the solution you'd like
Some way of registering global serializers.
We have several concerns regarding this:
- Implicitness – it is very easy to forget and hard to find which serializer is used for
UUID
in a particular class. Easy to mess/mix things up. - Security – you may accidentally deserialize data that you didn't want to in some area of the program
- Modules, incremental compilation – since serializers are compiled into the code by the plugin, changing global settings would require to recompile the whole module.
However, these problems may not concern small or mid-size applications. Probably this restriction would be relaxed in the future.
Valid concerns, but here are a couple of points from the other side.
-
Common types like BigDecimal, UUID, javax dates, etc are not supported at the moment. It's tedious and error prone to annotate every instance of these. These are extremely common base JDK types - in my code base for example a domain type could have 10 of these out of 30 fields, across hundreds of domain objects. Financial domain objects will use BigDecimals everywhere.
-
The most popular serialisation frameworks - think Jackson for example - handle serialization of any types. Jackson will serialize everything. So there must be a way to make this work.
Edited: Removed GSON from 2.
The Gson maintainers think point 2 is a failure of the library and have not supported this feature in Moshi (aka Gson v3). It has absolutely caused problems in practice.
Ok I stand corrected on Gson. It's still a feature / bug (delete as appropriate) of Jackson though.
I'm not sure whether it is actually valid (or easy to do), but package level annotations would be the way to go. I know it is possible in Java using package-info.java, but I've got no idea whether Kotlin could use such annotations in the compiler.
AFAIR, Kotlin doesn't support package-level annotation. It recognizes annotations on package-info.java
, but this requires enabling Java support in JVM target (hence, no MPP).
@sandwwraith Was afraid that was the case. The only other option I can come up with would be some way to add annotations to an annotationB
and have this annotation B
be used as a shorthand wherever needed.
Not global, but the file-level annotation @file:UseSerializers(MySerializer::class)
mitigates the problem. See Specifying serializers for a file.
Any update on this?
@nuhkoca Not for now, this problem was postponed
Wouldn't it be easy and logical to provide additional serializers to JsonBuilder? Currently only contextual and polyomorphic serializers can be provided...
@angryziber This would provide serializers for Json format, but not for e.g. Protobuf. I assume this issue is about specifying a serializer for a type for a whole module for all formats
Wouldn't it be easy and logical to provide additional serializers to JsonBuilder? Currently only contextual and polyomorphic serializers can be provided...
The "best" way would be to expose serializers in module metadata and then resolve them from this metadata. The question there is mainly how conflict resolution happens (which serializer should be used if there are multiple, and how can a developer override that - in some way the place would be some kind of module-info.kt
)
@sandwwraith as I understand, serializers are format-neutral, i.e. they describe the structure of the object and serialization can write/read different formats. I think I should have said SerializersModule
, not JsonBuilder
:-)
@pdvrieze I would even happily provide a list of global serializers to the kotlin-serialization plugin in build.gradle as a mapping of KClass -> Serializer, so that it could do it's compile time checking.
Anyway, SerializersModule.reflectiveOrContextual
is quite inefficient currently, as it looks for a precompiled serializer using reflection every time. It should cache already found serializers to reuse them more quickly. Maybe this is the reason why kotlin-serialization is actually slower than other Java json libraries.
These two issues stop me from using the library, unfortunately. Back to Jackson that doesn't support inline classes properly...
@angryziber What are you trying to achieve here? In particular, why are you using reflective or contextual serialization. The library is really designed for statically determining the serializer. However, it is reasonable that the lookup could be configured to use caching.
@pdvrieze static serialization sounds good to me, but I want to avoid defining custom serializers for useful 3rd-party classes over and over again in every class/file. This is a serious violation of DRY principle. I want to do it once per my gradle project.
Contextual kind of works for this purpose, but it still requires writing @Contextual everywhere and it's actual purpose is different - dynamic, but global definition of static serializers is needed.
is there any progress on this? either that or more built in serializers (for example BigDecimal, OffsetDateTime) would be really useful.
@christophsturm No, this feature is postponed
I feel that a config option could be added in the build.gradle(.kts)
.
@sandwwraith just a reminder that kolinx-serialization without this feature is unusable for a lot of developers on the JVM
Shameless plug! I feel my project, KotlinX Serialization Standard Serializers, solves these ergonomic issues by providing readily defined typealiases and serializers for many common JVM types (and adding more is welcome!)
The original sample would simply become the following, with no further configuration or definition of serializers required.
data class Foo(val decimal: BigDecimalAsDouble, val id: UuidAsString, val date: LocalDateTimeAsString)
Project status is currently listed as alpha but from my experience using it for the past half year it's been working well for the use-cases I've had.
The lack of java.time serializers out of the box makes it hard to demonstrate our open source library in Kotlin (in Kotlin Notebooks specifically). (Note that kotlinx.datetime is too weak for us, as it lacks the concept of Duration to represent the time between two LocalTime instances)
@ge0ffrey I believe that LocalTime arithmetic is intentionally omitted from kotlinx-datetime for the same reason as LocalDateTime — it is hard to do it correctly when accounting for DST gaps and overlaps: https://github.com/Kotlin/kotlinx-datetime?tab=readme-ov-file#date--time-arithmetic. Therefore, a conversion to Instant is recommended.
Regarding java.time serializers, I believe that we can repurpose corresponding kotlinx-datetime serializers in some point of future.