Skip to content

Delegate to default Serializer #1253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
timrijckaert opened this issue Dec 13, 2020 · 8 comments
Closed

Delegate to default Serializer #1253

timrijckaert opened this issue Dec 13, 2020 · 8 comments
Assignees

Comments

@timrijckaert
Copy link

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)
}
@sandwwraith
Copy link
Member

Related: #1169

Btw, in your case It is worth looking into json transformers rather than full-blown custom serializers

@timrijckaert
Copy link
Author

timrijckaert commented Dec 28, 2020

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

timrijckaert pushed a commit to timrijckaert/goplay-service that referenced this issue Dec 28, 2020
@timrijckaert
Copy link
Author

Hello?

@shanshin
Copy link
Contributor

Unfortunately, JsonTransformingSerializer does not currently support nullable types (you can't write something like JsonTransformingSerializer<Program?>(Program.serializer().nullable).

@ouchadam
Copy link

ouchadam commented Sep 2, 2021

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

@marcosalis
Copy link

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!

@sandwwraith
Copy link
Member

I think nullable JsonTransformingSerializer is a separate issue, can you please create one?

@sandwwraith
Copy link
Member

Fixed by #1169

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants