Delegate to default Serializer
Hi,
I'm dealing with an annoying JSON service which is out of my control.
If a value is unavailable it is expressed as an empty array [] instead of a canonical absence or null value.
I thought about making a custom JSON deserialiser and manually check if the value was present or not before delegating it to the default generated deserialiser.
However when defining a custom deserialiser it seems the generated deserialiser is not generated no more.
I made a simple test case here:
public object SomeObjSerializer : KSerializer<SomeObj> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("SomeObj")
override fun serialize(encoder: Encoder, value: SomeObj) {}
override fun deserialize(decoder: Decoder): SomeObj {
val input = decoder as JsonDecoder
val jsonObj = input.decodeJsonElement().jsonObject
return if (jsonObj["value"] is JsonPrimitive) {
//How to delegate to the default generated serializer?
} else {
SomeObj(null)
}
}
}
@Serializable(with = SomeObjSerializer::class)
public data class SomeObj(val value: String?)
public fun main() {
//language=JSON
val someJson = """{"value" : [] }"""
val clazz = Json.decodeFromString<SomeObj>(someJson)
//language=JSON
val someOtherJson = """{"value" : "42" }"""
val clazz2 = Json.decodeFromString<SomeObj>(someOtherJson)
}
Note that this example is a simple use case.
As a temporary solution I made a copy of my object without specifying the custom deserialiser which I reference in my own deserialiser and then map back to my original object.
However that of course does not seem to be a good solution.
public object SomeObjSerializer : KSerializer<SomeObj> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("SomeObj")
override fun serialize(encoder: Encoder, value: SomeObj) {}
override fun deserialize(decoder: Decoder): SomeObj {
val input = decoder as JsonDecoder
val jsonObj = input.decodeJsonElement().jsonObject
return if (jsonObj["value"] is JsonPrimitive) {
val obj = decoder.decodeSerializableValue(CopyOfSomeObjForDelegationPurposedOnly.serializer())
SomeObj(obj.value)
} else {
SomeObj(null)
}
}
}
@Serializable(with = SomeObjSerializer::class)
public data class SomeObj(val value: String?)
@Serializable
public data class CopyOfSomeObjForDelegationPurposedOnly(val value: String)
public fun main() {
//language=JSON
val someJson = """{"value" : [] }"""
val clazz = Json.decodeFromString<SomeObj>(someJson)
//language=JSON
val someOtherJson = """{"value" : "42" }"""
val clazz2 = Json.decodeFromString<SomeObj>(someOtherJson)
}
Related: #1169
Btw, in your case It is worth looking into json transformers rather than full-blown custom serializers
How can I return null?
public object ProgramSerializer : JsonTransformingSerializer<Program>(Program.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement =
if (element is JsonObject) {
element
} else {
null
}
}
@Serializable
public data class SomeObject(
@Serializable(with = ProgramSerializer::class)
val program: Program? = null,
)
I tried returning JsonNull but it fails.
kotlinx.serialization.json.internal.JsonDecodingException: Expected class kotlinx.serialization.json.JsonObject as the serialized body of xxx.xxxx.Program, but had class kotlinx.serialization.json.JsonNull
Hello?
Unfortunately, JsonTransformingSerializer does not currently support nullable types (you can't write something like JsonTransformingSerializer<Program?>(Program.serializer().nullable).
a temporary solution if you only need to deserialize, you can copy the logic from JsonTransformingSerializer
abstract class NullableJsonTransformingSerializer<T : Any>(
private val tSerializer: KSerializer<T>
) : KSerializer<T?> {
override val descriptor: SerialDescriptor get() = tSerializer.descriptor
final override fun deserialize(decoder: Decoder): T? {
require(decoder is JsonDecoder)
val element = decoder.decodeJsonElement()
return nullableTransformDeserialize(element)?.let { decoder.json.decodeFromJsonElement(tSerializer, it) }
}
protected open fun nullableTransformDeserialize(element: JsonElement): JsonElement? = element
final override fun serialize(encoder: Encoder, value: T?) {
throw IllegalAccessError("serialize not supported")
}
}
unfortunately serialize requires internal calls to writeJson
Hi there, I'm running into the same use case. Are there any plans to support nullable types with JsonTransformingSerializer with nullable types? It seems like a pretty common scenario, unless I'm missing an alternative way to do the same without the Json serializer.
The only way I can see around this for the time being (when serialization is also required, otherwise @ouchadam 's solution should work) is to replace the nullable type with a non-null one, and use a special Null "marker object" as model. But that's cumbersome and not always possible.
Thank you!
I think nullable JsonTransformingSerializer is a separate issue, can you please create one?