kotlinx.serialization icon indicating copy to clipboard operation
kotlinx.serialization copied to clipboard

Serializers are still required for generic arguments even if parameters are not used as property types

Open sandwwraith opened this issue 6 years ago • 6 comments
trafficstars

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<*>

sandwwraith avatar Nov 21 '19 14:11 sandwwraith

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.

Whathecode avatar Nov 21 '19 22:11 Whathecode

@sandwwraith Perhaps it would be interesting to add a NotSerializable serializer to the library.

pdvrieze avatar Nov 22 '19 13:11 pdvrieze

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.

Whathecode avatar Nov 22 '19 14:11 Whathecode

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.

joffrey-bion avatar Mar 02 '23 16:03 joffrey-bion

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.

bartvanheukelom avatar May 23 '23 12:05 bartvanheukelom

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,

Rattenkrieg avatar Aug 15 '24 21:08 Rattenkrieg