Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Laxystem opened this issue Jun 3, 2024 · 9 comments
Open
Labels

Comments

@Laxystem
Copy link

Laxystem commented Jun 3, 2024

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).

@pdvrieze
Copy link
Contributor

pdvrieze commented Jun 3, 2024

  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)

@Laxystem
Copy link
Author

Laxystem commented Jun 4, 2024

@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.

@pdvrieze
Copy link
Contributor

pdvrieze commented Jun 4, 2024

@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).

@Laxystem
Copy link
Author

Laxystem commented Jun 4, 2024

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

@pdvrieze
Copy link
Contributor

pdvrieze commented Jun 4, 2024

Go ahead

@Laxystem
Copy link
Author

Laxystem commented Jun 4, 2024

  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.

@pdvrieze
Copy link
Contributor

pdvrieze commented Jun 4, 2024

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

@Laxystem
Copy link
Author

Laxystem commented Jun 5, 2024

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.

@sandwwraith
Copy link
Member

Can't this be solved with just #292 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants