kotlinx.serialization
kotlinx.serialization copied to clipboard
Serializers are still required for generic arguments even if parameters are not used as property types
Consider the following (meaningless) class:
@Serializable
class Box<T>(val i: Int) {
fun T.withT() = i
}
If we try to use it
@Serializable
class Data3(val d: Box<*>)
We observe an error: Serializer has not been found for type 'Any?'. To use context serializer as a fallback, explicitly annotate type or property with @ContextualSerialization
Because star-projection approximated to Any? and Any? does not have a serializer. However, there are no properties of type T in class Box, so it still can be serialized.
Note that supporting such a case can dramatically complicate generic serializers design: currently, the generic serializer is required if a class has any type arguments, even if they are not used at all.
NB: this also happens in case of typealias SomeAlias = Box<*>
Ran into this as well. As a workaround I currently apply a NotSerializable dummy serializer to the type parameters.
A dummy serializer which can be applied to types that are never expected to be serialized, but for which the compiler is trying to generate/retrieve a serializer. E.g., types used as generic type parameters. Applying
@Serializable( with = NotSerializable::class )to those types ensures compilation succeeds, without having to actually make them serializable.
@sandwwraith Perhaps it would be interesting to add a NotSerializable serializer to the library.
A @NeverSerialized annotation would be more clear, which behind the scenes applies a NotSerializable serializer like I do manually now.
Serializable with NotSerializable just looks weird :), but works. I personally don't mind the 'opt-in' nature of this, and it is much easier to implement than full analysis of the code to see whether the type should ever be serialized.
I feel like the case for making Nothing serializable (https://github.com/Kotlin/kotlinx.serialization/issues/614) was actually really just this problem. Nothing cannot be used in anything that would actually be serialized (there is no value of this type, so none could ever be serialized). The problem people were facing with Nothing not being serializable is that they didn't have a way to specify @NeverSerialized on such type parameters.
Making Nothing serializable in 1.5.0 actually just solved the most pressing use case of this issue with generics (because Nothing is often used in generics, and specifically in cases where the generic type is never serialized). But all other use cases with user-defined classes will still hit the same issue. I wish Kotlinx Serialization wouldn't arbitrarily decide that every type parameter is going to be serialized (it's wrong to assume this as much as it's wrong to assume none of them are).
I really like the idea of having @NeverSerialized to tell the compiler plugin that a given type parameter will never be used for (de)serialization. Like @Whathecode, I also don't mind that it would be opt-in and not auto-detected. People will get a compile error, read about the issue, and then either provide a serializer or annotate @NeverSerialized depending on the use case. We still have the error by default to avoid accidental mistakes.
I'm experiencing the same issue with this class that encapsulates a script, where the type parameter indicates the result type of the script. The serialized form is simply the script text, so no serializer for R should be required at all.
@Serializable(with = SettingsScript.Companion::class)
data class SettingsScript<R : Any>(
val code: String,
) {
companion object : KSerializer<SettingsScript<Any>> {
...
}
}
By the way, what's funny is that there is no error serializing something like SettingsScript<(String) -> Int>, even though I have no idea what serializer<(String) -> Int> could possibly resolve to.
Can priority of this one be reevaluated given post 2.0.0 there is no way to do
class Wrapper<M>(...)
typealias NoopSerializableWrapper<M> = Wrapper<@Contextual M>
@Serializable
data class Test(val wrapper: NoopSerializableWrapper<Instant>)
I tried to achieve that in similar way as described in #2575 obviously with no luck,