Can't register both a default serializer and deserializer
See original GitHub issueI want to register a polymorphic KSerializer in a SerializersModule
. When encoding/decoding with JSON, I want to discriminate differently to when I am encoding in another format.
(I am trying to find a work around for https://github.com/Kotlin/kotlinx.serialization/issues/1664 - the non-accessible default ‘type’ field disrupts other systems.)
I’ve tried various combinations of registering serializers - with an annotation, in the serializer module using polymorphic or contextual - but it seems this isn’t possible.
val myProjectJsonSerializersModule = SerializersModule {
polymorphicDefaultDeserializer(Packet::class) { PacketJsonSerializer }
polymorphicDefaultSerializer(Packet::class) { PacketJsonSerializer }
}
// java.lang.IllegalArgumentException: Default serializers provider for
// class class Packet is already registered:
// (kotlin.String?) -> kotlinx.serialization.DeserializationStrategy<out Packet>?
(aside: it looks like there’s a typo in the error message, ‘class’ appears twice: “class class Packet”)
Expected behavior
Encoding/decoding with discriminators should be format-independent, and should not require hidden/non-accessible type fields.
Environment
- Kotlin version: 1.6.10
- Library version: 1.3.2
- Kotlin platforms: all
- Gradle version: 7.3.3
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.modules.SerializersModule
@Serializable
sealed class Packet {
@EncodeDefault
abstract val packetType: Type
enum class Type {
SOLID,
LIQUID,
GAS,
}
}
@Serializable
data class GasPacket(
val isToxic: Boolean,
) : Packet() {
@EncodeDefault
override val packetType: Type = Type.GAS
}
@Serializable
data class LiquidPacket(
val colour: String,
) : Packet() {
@EncodeDefault
override val packetType: Type = Type.LIQUID
}
@Serializable
data class SolidPacket(
val density: Long,
) : Packet() {
@EncodeDefault
override val packetType: Type = Type.SOLID
}
object PacketJsonSerializer : JsonContentPolymorphicSerializer<Packet>(
Packet::class
) {
private val key = Packet::packetType.name
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out Packet> {
val type: Packet.Type? = element
.jsonObject[key]
?.jsonPrimitive
?.contentOrNull
?.let { json ->
Packet.Type.values().firstOrNull { it.name == json }
}
requireNotNull(type) { "Unknown Packet.Type ${key}: $element" }
return when (type) {
Packet.Type.SOLID -> SolidPacket.serializer()
Packet.Type.LIQUID -> LiquidPacket.serializer()
Packet.Type.GAS -> GasPacket.serializer()
}
}
}
val jsonPacketMapper = Json {
prettyPrint = true
prettyPrintIndent = " "
serializersModule = SerializersModule {
polymorphicDefaultDeserializer(Packet::class) { PacketJsonSerializer }
polymorphicDefaultSerializer(Packet::class) { PacketJsonSerializer }
}
}
class TestDefaultSerDes {
private val gasPacket: Packet = GasPacket(
true
)
private val gasPacketJsonString =
"""
{
"isToxic": true,
"packetType": "GAS"
}
""".trimIndent()
@Test
fun expectEncodedJsonElementHasNoTypeDiscriminator() {
val encodedJsonElementGasPacket = jsonPacketMapper.encodeToJsonElement(gasPacket)
val expectedJsonElement = buildJsonObject {
put("isToxic", JsonPrimitive(true))
put("packetType", JsonPrimitive("GAS"))
}
assertEquals(expectedJsonElement, encodedJsonElementGasPacket)
}
@Test
fun expectJsonStringIsDecodedToPacket() {
val packet: Packet = jsonPacketMapper.decodeFromString(gasPacketJsonString)
assertEquals(Packet.Type.GAS, packet.packetType)
}
@Test
fun expectJsonStringIsDecodedToGasPacket() {
val gas: GasPacket = jsonPacketMapper.decodeFromString(gasPacketJsonString)
assertTrue(gas.isToxic)
assertEquals(Packet.Type.GAS, gas.packetType)
}
}
Issue Analytics
- State:
- Created 2 years ago
- Comments:10 (9 by maintainers)
Top GitHub Comments
Fix is available in the new 1.4.0-RC version
how can i use the merged solutions?