Ola Gawell
03/15/2022, 3:10 PMKSerializer
to handle the different subclasses. Code in thread.
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
Ola Gawell
03/15/2022, 3:11 PMclass 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()))
}
}
Adam S
03/15/2022, 9:31 PMIntValueProviderSerializer
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-typeAdam S
03/15/2022, 9:45 PMIntValueProviderSerializer
is right. It doesn't make sense to use an instance of Json inside a KSerializer.Adam S
03/15/2022, 10:26 PMValueBasedProvider
class, I adapted the code in the docs which will unbox the json value
@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))
}
Adam S
03/15/2022, 10:31 PMValueProvider
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!)
@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)
}
}
Adam S
03/15/2022, 10:32 PMOla Gawell
03/16/2022, 6:18 AMOla Gawell
03/16/2022, 6:58 AM