Thread
#serialization
    r

    rocketraman

    9 months ago
    I have a closed polymorphic structure like this (only one subclass shown for simplicity):
    @Serializable
    sealed class Foo<out T> {
      @Serializable
      data class Bar<out T>(
        val value: T?,
      ): Foo<T>()
    }
    Json.encodeToString(Foo.Bar(true))
    works, but if it is wrapped inside another Serializable e.g.:
    @Serializable
    data class Whatever(val foo: Foo<Boolean>)
    Json.encodeToString(Whatever(Foo.Bar(true)))
    produces the error:
    Exception in thread "main" kotlinx.serialization.SerializationException: Class 'Boolean' is not registered for polymorphic serialization in the scope of 'Any'.
    Mark the base class as 'sealed' or register the serializer explicitly.
    Dominaezzz

    Dominaezzz

    9 months ago
    Iirc there is a PR for this
    r

    rocketraman

    9 months ago
    Have a reference?
    I've done some poking around but can't find anything
    Dominaezzz

    Dominaezzz

    9 months ago
    r

    rocketraman

    9 months ago
    That's a similar case but has a completely different failure mode
    The specific case given in the test works just fine, so its not the same issue at all
    Dominaezzz

    Dominaezzz

    9 months ago
    Ah I see, have you tried registering a contextual serialiser for the subclass?
    r

    rocketraman

    9 months ago
    I have but either its not working or I haven't got the syntax right
    This is the work-around I expected to work, but the contextual provider is never even called:
    @Serializable
    data class Whatever(@Contextual val foo: Foo<Boolean>)
    
    val format = Json { SerializersModule {
      contextual(Foo::class) { args ->
        println("args: $args")
        // just testing, we know the type arg is Boolean for now
        Boolean.serializer()
      }
    } }
    I also tried:
    val format = Json { SerializersModule {
        polymorphic(Any::class) {
          subclass(Boolean::class, Boolean.serializer())
        }
      } }
    but this doesn't work either.
    Dominaezzz

    Dominaezzz

    9 months ago
    Oh not for Foo do it for Bar.
    r

    rocketraman

    9 months ago
    You mean like this?
    @Serializable
    sealed class Foo<out T> {
      @Serializable
      data class Bar<out T>(
        @Contextual val value: T?,
      ): Foo<T>()
    }
    
      val format = Json { SerializersModule {
        contextual(Foo.Bar::class) { args ->
          println("args: $args")
          Boolean.serializer()
        }
      } }
    If so, no, I tried that and it doesn't work either.
    Dominaezzz

    Dominaezzz

    9 months ago
    Thinking about how polymorphism currently works, what you want to do simply isn't possible.
    Create an issue I think.
    r

    rocketraman

    9 months ago
    That sucks. What is the best work-around in your opinion?
    Dominaezzz

    Dominaezzz

    9 months ago
    Not sure, do you really need the generics?
    r

    rocketraman

    9 months ago
    Only way I can avoid them is by a lot of code duplication.
    Dominaezzz

    Dominaezzz

    9 months ago
    Do you only serialise Boolean?
    r

    rocketraman

    9 months ago
    No. I have 3 subclasses of
    Foo
    , and I serialize a bunch of different types, one of which is Boolean, but could also be other primitives or data classes. Without generics I'd have to create 3 subclasses per type that is being serialized. Unless I'm missing something.
    Dominaezzz

    Dominaezzz

    9 months ago
    Yeah that's what I was going to suggest.
    Well, since it's a sealed class you could just write a custom serialiser.
    r

    rocketraman

    9 months ago
    That sounds promising. Let me read the docs on that.
    Wait, what do I need the custom serializer for? Serde already works when used directly on the sealed class. So, do I need to write a custom serializer for the wrapper?
    Dominaezzz

    Dominaezzz

    9 months ago
    Oh, it works directly? That's odd. I'm not sure then.
    r

    rocketraman

    9 months ago
    Yup, that's what is so weird about this. This works completely perfectly:
    Json.encodeToString(Foo.Bar(true))
    but:
    Json.encodeToString(Whatever(Foo.Bar(true)))
    does not.
    Well, a workaround appears to be to create a
    ValueHolder
    around the type T, so that T is bound by another sealed class hierarchy instead of just being
    Any
    .
    @Serializable
    sealed class ValueHolder<T> {
      abstract val value: T?
    }
    
    @Serializable
    data class BooleanValueHolder(override val value: Boolean?): ValueHolder<Boolean>()
    
    @Serializable
    sealed class Foo<T> {
      @Serializable
      data class Bar<T>(
        val value: ValueHolder<T>,
      ): Foo<T>()
    }
    Then serializing
    data class Whatever(val foo: Foo<Boolean>)
    works as expected.