spand
09/12/2023, 12:58 PM@Serializable
@SerialName("Box")
class Box<T>(val contents: T)
I expected to be able to combine built in sealed classes and container support ie.:
@Serializable
@SerialName("Either")
sealed class Either<L, R> {
@Serializable
class Left<L, R>(val d: L) : Either<L, R>()
@Serializable
class Right<L, R>(val d: R) : Either<L, R>()
}
@Serializable
data class SerTest(
val either: Either<String, Int>
)
And used as:
@Test
fun foo() {
val d = SerTest(Either.Left("left"))
val str = Json.encodeToString(d)
assertEquals("""{"either":{"d":"left"}}""", str)
}
Fails with:
kotlinx.serialization.SerializationException: Class 'String' is not registered for polymorphic serialization in the scope of 'Any'.
To be registered automatically, class 'String' has to be '@Serializable', and the base class 'Any' has to be sealed and '@Serializable'.
Alternatively, register the serializer for 'String' explicitly in a corresponding SerializersModule.
But I cannot get it to work. Do I have to write my own serializer or am I missing something ?pdvrieze
09/13/2023, 9:48 AMspand
09/13/2023, 9:49 AMpdvrieze
09/13/2023, 9:51 AMJson.encodeToString(Either.Left("Hello"))
work?spand
09/13/2023, 9:55 AMpdvrieze
09/13/2023, 10:07 AMAny
. For security purposes the serialization library doesn't automatically serialize/deserialize subclasses for non-sealed parents. The solution is to either add the requirements to the serializers module (per the error), or to have more specific member types.pdvrieze
09/13/2023, 10:08 AMSerTest
is String/Int.spand
09/13/2023, 10:39 AM@Serializable
data class BoxTest(
val box: Box<String>
)
@Test
fun boxtest() {
val d = BoxTest(Box("left"))
val str = Json.encodeToString(d)
assertEquals("""{"box":{"contents":"left"}}""", str)
}
spand
09/13/2023, 10:41 AMKSerializer
that implements this behavior which I expected to be quite standard and supported out of the boxspand
09/13/2023, 11:49 AM@OptIn(InternalSerializationApi::class)
class Either2Serializer<L, R>(
lSerializer: KSerializer<L>,
rSerializer: KSerializer<R>,
) : KSerializer<Either<L, R>> {
private val leftSerializer = Either.Left.serializer(lSerializer, rSerializer)
private val rightSerializer = Either.Right.serializer(lSerializer, rSerializer)
@Suppress("UNCHECKED_CAST")
private val impl = SealedClassSerializer(Either::class.qualifiedName!!,
Either::class,
arrayOf(Either.Left::class, Either.Right::class),
arrayOf(leftSerializer, rightSerializer)) as SealedClassSerializer<Either<L, R>>
override val descriptor
get() = impl.descriptor
override fun deserialize(decoder: Decoder): Either<L, R> = impl.deserialize(decoder)
override fun serialize(encoder: Encoder, value: Either<L, R>) = impl.serialize(encoder, value)
}
pdvrieze
09/13/2023, 12:20 PMEitherSerializer
is effectively a polymorphic serializer. This means there needs to be a way to mark left vs right. (The same way that nullability has a nullMarker). Unfortunately there is no standard approach for this that formats could then use to elide the marker if not needed, unless you actually use a polymorphic serializer.