I encountered a problem with data serialization. The REST API is not mine and I can't change it. For a certain request I receive a Object:
data class State(
val instance: String,
val value: Any
)
Various types of data can come as the value parameter of an object, such as Boolean, String, Int, Float
and even Object
, for exemple:
"state": {
"instance": "rgb",
"value": 13910520
}
// OR
"state": {
"instance": "hsv",
"value": {
"h": 255,
"s": 100,
"v": 50
}
}
In this case, the value type depends on instance (instance is always a String type). With the help of this answer, I was more or less able to understand the various types, but I did not understand how I should work if receive an Object in response.
@Serializable(with = StateSerializer::class)
data class StateObject(
val instance: String,
val value: Any
)
object StateSerializer : KSerializer<StateObject> {
private val dataTypeSerializers: Map<String, KSerializer<Any>> =
mapOf(
"..." to serialDescriptor<Boolean>(),
"..." to serialDescriptor<String>(),
"..." to serialDescriptor<Int>()
).mapValues { (_, v) -> v as KSerializer<Any> }
private fun getValueSerializer(instance: String): KSerializer<Any> =
dataTypeSerializers[instance] ?: throw SerializationException()
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("StateObject") {
element("instance", serialDescriptor<String>())
element("value", buildClassSerialDescriptor("Any"))
}
override fun deserialize(decoder: Decoder): StateObject = decoder.decodeStructure(
descriptor) {
if (decodeSequentially()) {
val instance = decodeStringElement(descriptor, 0)
val value = decodeSerializableElement(
descriptor,
1,
getValueSerializer(instance)
)
StateObject(instance, value)
} else {
require(decodeElementIndex(descriptor) == 0) { }
val instance = decodeStringElement(descriptor, 0)
val value = when (val index = decodeElementIndex(descriptor)) {
1 -> decodeSerializableElement(descriptor, 1, getValueSerializer(instance))
CompositeDecoder.DECODE_DONE -> throw SerializationException("value field is missing")
else -> error("Unexpected index: $index")
}
StateObject(instance, value)
}
}
override fun serialize(encoder: Encoder, value: StateObject) {
encoder.encodeStructure(descriptor) {
encodeStringElement(descriptor, 0, value.instance)
encodeSerializableElement(
descriptor,
1,
getValueSerializer(value.instance),
value.value
)
}
}
}
That instance
field looks like your class discriminator.
In that case you can use it for built in polymophic handling without the need for writing custom serializer:
@OptIn(ExperimentalSerializationApi::class)
@JsonClassDiscriminator("instance")
@Serializable
sealed class State {
abstract val value: Any
@Serializable
@SerialName("rgb")
data class RGB(override val value: Int) : State()
@Serializable
@SerialName("hsv")
data class HSV(override val value: HSValues) : State()
// declare all possible types
}