question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Can't register both a default serializer and deserializer

See original GitHub issue

I 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:closed
  • Created 2 years ago
  • Comments:10 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
sandwwraithcommented, Aug 3, 2022

Fix is available in the new 1.4.0-RC version

0reactions
Omara8commented, Jul 29, 2022

how can i use the merged solutions?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Is it possible to use a custom serializer/deserializer for a type ...
You need to create new com.fasterxml.jackson.databind.module.SimpleModule instance and register all custom serialisers and deserialisers.
Read more >
How to implement Kafka Serializers and Deserializers?
Custom serializers and deserializers cannot be CDI beans. Kafka instantiates them directly using reflection. Conclusion. This post explores ...
Read more >
Avro Schema Serializer and Deserializer
Avro defines both a binary serialization format and a JSON serialization format. ... The following examples use the default Schema Registry URL value ......
Read more >
Custom Serializers in Apache Kafka - Baeldung
There are there methods available to override for both interfaces: configure: used to implement configuration details; serialize/deserialize: ...
Read more >
Small question about serialization of implicitly registered classes
If you register with IDs, whether explicitly or implicitly, you must have the exact same IDs when you deserialize. When you register implicitly,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found