I'm working with an API that returns an empty string instead of null to indicate the existence of a property, i.e.:
{
"id": 1,
"child": { "foo": "bar" } // child exists
}
{
"id": 1,
"child": "" // child does not exist
}
This does not play nice with a serializable data class:
@Serializable
data class Parent(val id: Int, val child: Child?)
@Serializable
data class Child(val foo: String)
// Error when child is empty string:
kotlinx.serialization.json.internal.JsonDecodingException: Expected class
kotlinx.serialization.json.JsonObject (Kotlin reflection is not available) as
the serialized body of com.mypackage.Parent, but had class
kotlinx.serialization.json.JsonLiteral (Kotlin reflection is not available)
I "fixed" this issue by writing a custom serializer:
@Serializable(Parent.Companion::class)
data class Parent(val id: Int, val child: Child?) {
companion object : KSerializer<Parent> {
override val descriptor = PrimitiveSerialDescriptor(
"Parent",
PrimitiveKind.STRING
)
override fun deserialize(decoder: Decoder): Parent {
val decoder = decoder as? JsonDecoder
?: throw SerializationException("Expected JSON decoder.")
val jsonObject = decoder.decodeJsonElement() as? JsonObject
?: throw SerializationException("Expected JSON object at root.")
if (jsonObject.containsKey("id").not()) throw SerializationException(
message = "Field 'id' is missing."
)
val childElement = jsonObject.get("child")
val child: Child?
if (childElement == null || childElement is JsonPrimitive) {
child = null
} else {
child = decoder.json.decodeFromJsonElement(childElement)
}
return Parent(id, child)
}
override fun serialize(encoder: Encoder, value: Parent) {
Parent.serializer().serialize(encoder, value)
}
}
}
However, this is fairly tedious and potentially untenable for a data class with many properties. What I'd like to do is have the ability to opt-in individual properties to perform the check from the serialization code above, possibly with an annotation:
@Serializable
data class Parent(
val id: Int,
@NullOnSerializationFail val child: Child?
)
I'm new to Kotlin and haven't programmed in a Java environment for some time. Coming from Swift, something like this could be accomplished with a property wrapper, but from what I can gather, Kotlin/Java annotations don't work the same way.
Any ideas on how I could perform these kind of checks without writing per-class serialization code?