Wrapping a type's serializer with another without changing the actual type
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:
- Via an inline class wrapping the original type --
Functional<T>instead ofT.- Problematic, I have to access the value via
obj.property.valueinstead of justobj.property.
- Problematic, I have to access the value via
- Using
@Serializable(with = Functional::class).- Much longer, isn't pretty, and it's easy to import the wrong
Functional.
- Much longer, isn't pretty, and it's easy to import the wrong
- 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
- This approach looks as if it would work perfectly, however, the following code results in an error.
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).
- 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 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 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 thanks! Would you mind me using it as a reference for a MPL 2.0-licensed project?
Go ahead
- 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 You can either use annotations with @SerialInfo as meta-annotation. Those will turn up in the descriptors.
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.
Can't this be solved with just #292 ?