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

Wrapping a type's serializer with another without changing the actual type

Open Laxystem opened this issue 1 year ago • 9 comments

What is your use-case and why do you need this feature?

I am implementing a JsonLD library for Kotlin/Multiplatform. However, I've encountered a problem: expanded JsonLD has lots of "useless" contracts like [{"@value":"etc..."}], meaning just "etc...".

To solve this issue, I create custom serializers that wrap the original type's serializer. There are three ways I can do so:

  1. Via an inline class wrapping the original type -- Functional<T> instead of T.
    • Problematic, I have to access the value via obj.property.value instead of just obj.property.
  2. Using @Serializable(with = Functional::class).
    • Much longer, isn't pretty, and it's easy to import the wrong Functional.
  3. Use typealiases. For example,
    typealias LdList = @Serializable(with = LdListSerializer::class) ImmutableList
    
    internal class LdListSerializer<E>(val impl: KSerializer<E>) : KSerializer<ImmutableList<E>> { /*...*/ }
    
    • This approach looks as if it would work perfectly, however, the following code results in an error.
      // Type alias expands to T, which is not a class, an interface, or an object.
      public typealias Functional<T> = @Serializable(with = FunctionalSerializier::class) T
      

Describe the solution you'd like

My initial solution

Add the with parameter to MetaSerializable, and allow serializers to get the serializer of the type they're of (serializer<T>() given @Functional T) as a constructor parameter.

And why it potentially won't work

The first part of my solution is alright. The second part, however, can be argued to be problematic design-wise - it could lead to annotations being chained order-dependently (something never seen before in Kotlin nor Java as far as I'm aware).

An alternative

Wait for Kotlin to support expanding type aliases to type parameters (see KT-68757).

Laxystem avatar Jun 03 '24 14:06 Laxystem

  1. Write a format that delegates to Json, but wraps serializers as required. (this is not as hard as it sounds, although some functions need to wrap their return types)

pdvrieze avatar Jun 03 '24 15:06 pdvrieze

@pdvrieze although that makes sense for my use case (and I would love to know how to do that), it won't make sense for all use cases.

Laxystem avatar Jun 04 '24 07:06 Laxystem

@Laxystem Have a look at https://gist.github.com/pdvrieze/0eb34ab2f914383a4d98cfc414272ac7 This is a (simple) abstract class for StringFormat that has two abstract methods (determineEffectiveSerializer and determineEffectiveDeserializer). You could implement those to override the actual serializer used. The initial serializer/deserializer are "hacked" wrapped just to inject the actual format wrappers.

For some formats (such as XML) you might also need to handle wrapping the SerialDescriptors. For example XML build a document structure before actually (de)serializing. (Note that this particular class isn't needed for XML as XML has a policy that allows overriding (de)serializers already).

pdvrieze avatar Jun 04 '24 15:06 pdvrieze

@pdvrieze thanks! Would you mind me using it as a reference for a MPL 2.0-licensed project?

Laxystem avatar Jun 04 '24 16:06 Laxystem

Go ahead

pdvrieze avatar Jun 04 '24 16:06 pdvrieze

  1. Write a format that delegates to Json, but wraps serializers as required.

Problem: how can I identify when it is required? Intuitively, I think of annotations - but is there a better way? Afterall, afaik I can't use the .deocde/.encode functions to check, as I have to use them in order, and each only once.

Laxystem avatar Jun 04 '24 17:06 Laxystem

@Laxystem You can either use annotations with @SerialInfo as meta-annotation. Those will turn up in the descriptors.

pdvrieze avatar Jun 04 '24 21:06 pdvrieze

Got it, thanks. I'll just add List, Set, and Map to the serializer module, so that I'll be able to check if a serializer is of them.

Laxystem avatar Jun 05 '24 03:06 Laxystem

Can't this be solved with just #292 ?

sandwwraith avatar Oct 04 '24 14:10 sandwwraith