Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.44k stars 624 forks source link

Override JsonContentPolymorphicSerializer with SerializersModule #2340

Closed todoFixIt closed 1 year ago

todoFixIt commented 1 year ago

Let's say I have a library that has an interface and an implementation of that interface, that library provides a default serialization for that interface which is the implementation it has. Now I want to use that library and override that serializer since I may have multiple different implementations of that interface and the default serializer is not longer enough, and also I want it to work in the Json SerializersModule construction instead of inline in each call to encode/decode since I will be passing the Json through.

The library code

 @Serializable(with = LibrarySerializer::class)
  interface SomeInterface {
      val someProperty: String
  }

  @Serializable
  class LibraryImpl(
      override val someProperty: String
  ) : SomeInterface

  object LibrarySerializer :
      JsonContentPolymorphicSerializer<SomeInterface>(SomeInterface::class) {
      override fun selectDeserializer(element: JsonElement) = LibraryImpl.serializer()
  }

The app code

@Serializable
  class AppImpl(
      override val someProperty: String
  ) : SomeInterface

  object AppSerializer :
      JsonContentPolymorphicSerializer<SomeInterface>(SomeInterface::class) {
      override fun selectDeserializer(element: JsonElement) {
          when {
              someCondition -> AppImpl.serializer()
              else -> LibraryImpl.serializer()
          }
      }
  }

What I would want in the app code

 Json {
      serializersModule = SerializersModule {
          // Something like this that will override the use of 
          // @Serializable(with = LibrarySerializer::class) with the one specified here
          polymorphic(SomeInterface::class, AppSerializer)
      }
  }
sandwwraith commented 1 year ago

interface with @Serializable(with) is treated the same way as class with custom serializer: this serializer became default. Currently, we do not have provide ways to override default serializer globally. You can use any customization technique we already have: @Serializable on property, @file:UseSerializers, pass serializer in encodeToString explicitly, etc.

Also #2060 (but it's about polymorphic-by-default interfaces)

todoFixIt commented 1 year ago

Even if you can't override it at least is there any way currently to achieve what I want? that is for the library to provide an implementation (even if not setted) and the client to either use it or create is own, but at the Json level.

sandwwraith commented 1 year ago

@todoFixIt See this section: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#passing-a-serializer-manually and sections below ('Specifying serializer on a property', 'Specifying serializer for a file', 'Specifying serializer globally using typealias')

sandwwraith commented 1 year ago

If you do not have further questions, I'll close this one

todoFixIt commented 1 year ago

Sorry for the delay but I couldn't resolve the issue with the doc you provided, and I think you currently can't, that's why I opened this as a feature (correct me if I'm wrong and there is a way).

I'll show my current simplified code hoping is enough to understand why I mean:

This next code is on a library

interface IApiError {
    val message: String
}
interface IApiResponse {
    val errors: List<IApiError>
}
data class ApiError(override val message): IApiError {}
data class ApiResponse(
    override val errors: List<IApiError>,
) : IApiResponse { }

The next code is on my current project

sealed class NetworkError(open val message: String): IApiError {
    data class Timeout(override val message: String): NetworkError(message)
}

// Some API Call that uses Retrofit and serializes using a Json instance (one for the whole project)
// implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
fun someApiCall(): Call<ApiResponse>

I need to be able to serialize IApiError to ApiError and to NetworkError. The API wont return any kind of discriminator so my current way of serializing it is by using JsonContentPolymorphicSerializer.

The issue is that unless I'm wrong this can only be specified on the @Serialize annotation and that isn't possible if I want both to serialize ApiError and NetWork response since the former is on the library an the later on my project, and I didn't find a way to specify JsonContentPolymorphicSerializer at the Json ¿How would you do resolve this?

sandwwraith commented 1 year ago

I see. It is a duplicate of #2060 then.

todoFixIt commented 1 year ago

@sandwwraith Oh I see, thank you, so in 1.6.0 this will be possible, Do you have an estimate of when that would be approximately?

sandwwraith commented 1 year ago

Unfortunately we have to postpone this after 1.6.0, as this is a large change in behavior that also affects compiler plugin

todoFixIt commented 1 year ago

Ok, if there is no current workaround I guess you could close this, thank you for your time.