In my API, there are many JSON values that come as a JSON object with value
, description
, such as this:
{
id: 1321,
season: {
value: *some value*,
description: *some description*
},
...,
type: {
value: *some value*,
description: *some description*
}
}
The value
field is always some Enum, and description
doesn’t carry any informative value for me.
To get free of unnecessary nesting I tried to make a custom serializer, and it works fine:
@OptIn(ExperimentalSerializationApi::class)
open class EnumSerializer<T : Enum<T>>(
private val serializer: KSerializer<T>
) : KSerializer<T> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor(serializer.descriptor.serialName) {
element("value", serializer<String>().descriptor)
element("description", serializer<String>().descriptor)
}
override fun serialize(encoder: Encoder, value: T) {
encoder.encodeSerializableValue(serializer, value)
}
override fun deserialize(decoder: Decoder): T {
return decoder.decodeStructure(descriptor) {
lateinit var value: String
while (true) {
when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.DECODE_DONE -> break
0 -> value = decodeStringElement(descriptor, i)
1 -> decodeStringElement(descriptor, i)
else -> throw SerializationException("Unknown index $i")
}
}
defaultJson.decodeFromString(serializer, value)
}
}
}
I then use it as follows:
internal object SeasonEnumSerializer : EnumSerializer<SeasonApi>(SeasonApi.generatedSerializer())
@OptIn(ExperimentalSerializationApi::class)
@Serializable(with = SeasonEnumSerializer::class)
@KeepGeneratedSerializer
enum class SeasonApi(val value: String) {
@SerialName("winter") WINTER("winter"),
@SerialName("spring") SPRING("spring"),
@SerialName("summer") SUMMER("summer"),
@SerialName("autumn") AUTUMN("autumn");
override fun toString(): String = value
}
@Serializable
data class ReleaseApi(
@SerialName("id") val id: Int,
@SerialName("season") val season: SeasonApi?,
...
@SerialName("type") val type: ReleaseTypeApi?,
...
)
However, the serializer behavior breaks down when null values come from the API:
{
value: null,
description: null
}
I marked the season
and type
fields as nullable in my ReleaseApi
model. I also set isLenient
, ignoreUnknownKeys
and coerceInputValues
flags. But it doesn’t work.
How should I handle null values?