Our API returns a reserved_stock
field that can be either a String or an Int (i.e. "158" or "5"). Since I cannot alter the API's response directly, I have to figure out a way to handle both types.
What would be the right way to do this using Kotlinx.Serialization? As of right now, I have tried declaring the field as a sealed class type and then use a custom Serializer to decode the actual value. Something along the lines of:
@Serializable(with = ReservedStockSerializer::class)
sealed class ReservedStock {
@Serializable
data class IntValue(val value: Int) : ReservedStock()
@Serializable
data class StringValue(val value: String) : ReservedStock()
}
@Serializable
object ReservedStockSerializer : KSerializer<ReservedStock> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ReservedStock", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: ReservedStock) {
val stringRepresentation = when (value) {
is ReservedStock.IntValue -> value.value.toString()
is ReservedStock.StringValue -> value.value
}
encoder.encodeString(stringRepresentation)
}
override fun deserialize(decoder: Decoder): ReservedStock {
val composite = decoder.beginStructure(descriptor)
var intValue: Int? = null
var stringValue: String? = null
loop@ while (true) {
when (val index = composite.decodeElementIndex(descriptor)) {
CompositeDecoder.DECODE_DONE -> break@loop
else -> {
when (index) {
0 -> intValue = composite.decodeIntElement(descriptor, index)
1 -> stringValue = composite.decodeStringElement(descriptor, index)
}
}
}
}
composite.endStructure(descriptor)
return if (intValue != null) {
ReservedStock.IntValue(intValue)
} else {
ReservedStock.StringValue(stringValue ?: "")
}
}
}
This does not seem to work however, since it throws a JsonException stating that it was expecting an object but instead found an Int (or String).
In order to make it work, i had to specify isLenient=true
in the JsonConfig since our API returns unquoted values.
I also created the following trivial JsonTransformingSerializer
, like so:
object ReservedStockSerializer : JsonTransformingSerializer<String>(String.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement {
return if(element !is JsonPrimitive) JsonPrimitive("N/A") else element
}
}
and applied it to the field in question like so:
...
@Serializable(ReservedStockSerializer::class)
@SerialName("reserved_stock")
val reservedStock: String,
...