Hi! I get this error when trying to do a polymorph...
# serialization
o
Hi! I get this error when trying to do a polymorphic deserialization of a class with generic typing. I am using a custom serializer
KSerializer
to handle the different subclasses. Code in thread.
Copy code
java.lang.AssertionError: No such value argument slot in IrConstructorCallImpl: 0 (total=0).
Symbol: /IntValueProviderSerializer.<init>|-5645683436151566731[0]
We are using a multiplatform project with
org.jetbrains.kotlinx:kotlinx-serialization-json: 1.3.2
Copy code
class Input {
    fun <T> useData(data: T): T {
        return data
    }
}

abstract class ValueProvider<T> {
    abstract fun getValue(input: Input): T
}

@kotlinx.serialization.Serializable
class InputBasedProvider<T>(
    val data: T
) : ValueProvider<T>() {
    override fun getValue(input: Input): T {
        return input.useData(data)
    }
}

@kotlinx.serialization.Serializable
data class ValueBasedProvider<T>(
    val value: T
) : ValueProvider<T>() {
    override fun getValue(input: Input): T = value
}


@Serializable
data class DataHolder(
    @kotlinx.serialization.Serializable(with = IntValueProviderSerializer::class)
    val myData: ValueProvider<Int>,
)

class IntValueProviderSerializer : KSerializer<ValueProvider<Int>> {
    private val jsonParser = Json { isLenient = true }

    override fun deserialize(decoder: Decoder): ValueProvider<Int> {
        return when (val element = decoder.decodeSerializableValue(JsonElement.serializer())) {
            is JsonObject -> jsonParser.decodeFromString(InputBasedProvider.serializer(Int.serializer()), element.toString())
            else -> ValueBasedProvider(<http://element.jsonPrimitive.int|element.jsonPrimitive.int>)
        }
    }

    override val descriptor: SerialDescriptor = InputBasedProvider.serializer(Int.serializer()).descriptor

    override fun serialize(encoder: Encoder, value: ValueProvider<Int>) {
        // Skipped fow now
    }
}

class SmallTest {

    private val json1 = """{ "myData": 5 }""".trimIndent()
    private val json2 = """{ "myData": { "data": 9 } }""".trimIndent()

    @Test
    fun parseSimpleValue() {
        val dataHolder = Json.decodeFromString(DataHolder.serializer(), json1)
        assertEquals(5, dataHolder.myData.getValue(Input()))
    }

    @Test
    fun parseComplexValue() {
        val dataHolder = Json.decodeFromString(DataHolder.serializer(), json2)
        assertEquals(9, dataHolder.myData.getValue(Input()))
    }
}
a
it looks like you're using
IntValueProviderSerializer
as the serializer for a generic property -
ValueProvider<Int>
I think because
IntValueProviderSerializer
needs a constructor parameter to specify the serializer for the data type (which in your case is always Int, but the parameter still needs to be there) What happens if you add a single constructor parameter? https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#custom-serializers-for-a-generic-type
I don't think the deserialize function in your
IntValueProviderSerializer
is right. It doesn't make sense to use an instance of Json inside a KSerializer.
okay I think this should work for you basically, for your
ValueBasedProvider
class, I adapted the code in the docs which will unbox the json value
Copy code
@Serializable(with = ValueBasedProviderSerializer::class)
data class ValueBasedProvider<T>(
  val value: T
) : ValueProvider<T>() {
  override fun getValue(input: Input): T = value
}

/**
 * See <https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#custom-serializers-for-a-generic-type>
 */
class ValueBasedProviderSerializer<T>(
  private val dataSerializer: KSerializer<T>
) : KSerializer<ValueBasedProvider<T>> {

  override val descriptor: SerialDescriptor = dataSerializer.descriptor

  override fun serialize(encoder: Encoder, value: ValueBasedProvider<T>) =
    dataSerializer.serialize(encoder, value.value)

  override fun deserialize(decoder: Decoder) =
    ValueBasedProvider(dataSerializer.deserialize(decoder))
}
figuring out the serializer for
ValueProvider
was tricky. Normally there's
JsonContentPolymorphicSerializer
- but that doesn't work for generic classes. So I copied the class, and changed it so it would be like the generic serializer example it selects the deserialization based on the json content (see
JsonContentPolymorphicSerializer
), and serialises based on the specific Kotlin subclass of
ValueProvider
(make it a
sealed class
and this will be nicer!)
Copy code
@Serializable
data class DataHolder(
  @Serializable(with = ValueProviderSerializer::class)
  val myData: ValueProvider<Int>,
)

class ValueProviderSerializer<T>(
  private val dataSerializer: KSerializer<T>
) : KSerializer<ValueProvider<T>> {

  override val descriptor: SerialDescriptor = dataSerializer.descriptor

  override fun serialize(encoder: Encoder, value: ValueProvider<T>) {
    when (value) {
      is ValueBasedProvider -> ValueBasedProvider.serializer(dataSerializer)
        .serialize(encoder, value)
      is InputBasedProvider -> InputBasedProvider.serializer(dataSerializer)
        .serialize(encoder, value)
      else                  -> throw SerializationException("unknown ValueProvider type $value")
    }
  }

  override fun deserialize(decoder: Decoder): ValueProvider<T> {

    val jsonDecoder =
      decoder as? JsonDecoder ?: throw SerializationException("can only handle Json decoding")

    val element = jsonDecoder.decodeJsonElement()

    val serializer = when (element) {
      is JsonPrimitive -> ValueBasedProvider.serializer(dataSerializer)
      is JsonObject    -> InputBasedProvider.serializer(dataSerializer)
      is JsonArray     -> throw SerializationException("can't handle arrays")
    }

    return jsonDecoder.json.decodeFromJsonElement(serializer, element)
  }

}
full code
o
Wow, amazing! Thank you! Will try it out immediately.
It worked perfectly! I would never have solved it by my own. Thank you!
🎉 1
👌 1