Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.36k stars 619 forks source link

Include type alias annotations in `SerialDescriptor.annotations` #2702

Open Laxystem opened 3 months ago

Laxystem commented 3 months ago

I've only seen documentation for .PROPERTY and .CLASS.

sandwwraith commented 3 months ago

No, we currently do not support @SerialInfo-annotations used on types.

Laxystem commented 3 months ago

No, we currently do not support @SerialInfo-annotations used on types.

Ic. Ig this issue has now become a feature request - to support @SerialInfo annotations on TYPE_ALIASes or TYPEs.

pdvrieze commented 3 months ago

To implement this would require updates to SerialDescriptor (in the library) and the compiler plugin. As such it might be helpful to know what your use case is, and what your intended semantics are:

Considering that the serial descriptor is linked to the serializer what you are effectively asking for is some sort of lightweight custom serializer (there is a naming problem here as well - names are supposed to be unique, and some format rely on that fact for caching)

Laxystem commented 3 months ago

As such it might be helpful to know what your use case is, and what your intended semantics are:

public typealias LangString = @NonLinkedJson(wrapWithValueObject = false) @Serializable(with = LangStringSerializer::class) ImmutableMap<Locale?, ImmutableList<String>>

Because it's much more flexible than creating a value class.

If it is a generic type parameter, how are you going to expose that? There is a naming problem here as well - names are supposed to be unique, and some format rely on that fact for caching.

One could support type aliases, instead of types, and then just use the typealias' name.

Laxystem commented 3 months ago

An alternative would be to support an annotations parameter on SerialDescriptor(serialName: String, original: SerialDescriptor).

pdvrieze commented 3 months ago

@Laxystem You can already use buildSerialDescriptor to build fully custom descriptors (including ones that just copy from the original) - even implementing the interface works (yes I'm aware these are hidden behind experimental flags - this is out of caution, to allow for API changes, not because the feature is going to suddenly disappear). Then you can have (LinkedLangStringSerializer and NonlinkedLangStringSerializer)

Laxystem commented 3 months ago

@Laxystem You can already use buildSerialDescriptor to build fully custom descriptors (including ones that just copy from the original) - even implementing the interface works (yes I'm aware these are hidden behind experimental flags - this is out of caution, to allow for API changes, not because the feature is going to suddenly disappear). Then you can have (LinkedLangStringSerializer and NonlinkedLangStringSerializer)

Ic, thanks. I'll keep this open as a feature request though

sandwwraith commented 3 months ago

The problem with this feature is that in case of usages like

@Serializable
class X

@Serializable
class A(val s: List<@SomeInfo X>, val e: List<@SomeOtherInfo X>)

Serial descriptor for X is not created every time, it is simply taken from X.serializer(), because it is constructed and filled there. It is possible to come up with some technical solution to re-wrap it with some additional annotation info of course, but it is a lot of work for such a narrow-scoped feature

Laxystem commented 3 months ago

Serial descriptor for X is not created every time, it is simply taken from X.serializer(), because it is constructed and filled there. It is possible to come up with some technical solution to re-wrap it with some additional annotation info of course, but it is a lot of work for such a narrow-scoped feature

That's why I'm proposing to support type aliases and not all types.

typealias XWithInfo = @SomeInfo X

// compiles into

typealias XWithInfo = @Serializable(XWithInfoSerializer::class) X

object XWithInfoSerializer : KSerializer<X> by serializer<X>() {
    override val descriptor = this@KSerializer.descriptor.withAnnotation(SomeInfo())
}

// which in turn compiles into replacing all usages of XWithInfo with @Serializable(XWithInfoSerializer::class) X
sandwwraith commented 3 months ago

Type aliases aren't really compiled into anything, they just dissolve into thin air :) I'm not sure if it is possible to provide such substitution with a compiler plugin

Laxystem commented 3 months ago

Type aliases aren't really compiled into anything, they just dissolve into thin air :) I'm not sure if it is possible to provide such substitution with a compiler plugin

That's the thing, what I'm suggesting is to create custom serializers with the typealias' serial name, that way, you have no conflicts.

pdvrieze commented 3 months ago
typealias XWithInfo = @SomeInfo X

// compiles into

typealias XWithInfo = @Serializable(XWithInfoSerializer::class) X

This would be like an extension of MetaSerializable (like the custom/serializer factory behaviour originally discussed when that annotation was introduced).

object XWithInfoSerializer : KSerializer<X> by serializer<X>() {
    override val descriptor = this@KSerializer.descriptor.withAnnotation(SomeInfo())
}

This is not actually valid, the descriptor must have a unique serialName. I'm also not sure that the SomeInfo annotation needs to be attached to the type/serializer rather than the use site.

Laxystem commented 2 months ago

This would be like an extension of MetaSerializable (like the custom/serializer factory behaviour originally discussed when that annotation was introduced). Yes, that was the original idea.

This is not actually valid, the descriptor must have a unique serialName. I'm also not sure that the SomeInfo annotation needs to be attached to the type/serializer rather than the use site.

Yes; and type aliases have a full, unique name, with the package and everything.