Hi everybody! Is it possible to parse an empty str...
# serialization
d
Hi everybody! Is it possible to parse an empty string to null? Context: I’m trying to parse a response from an external version-check api. If version is up to date it returns a 200 response + empty json body. If the version is outdated it returns HTTP 200 + json body. Here is an oversimplified test:
Copy code
@Test
    fun test() {
        val voluntary = Json.decodeFromString<SerializableVersionCheckResponse?>("""
        |{
        |  "beheerCode": "VRIJWILLIGE_UPDATE",
        |  "bericht": "App moet geupdate worden"
        |}
        """.trimMargin()
        )
        assertNotNull(voluntary)
        assertEquals("VRIJWILLIGE_UPDATE", voluntary.beheerCode)

        val mandatory = Json.decodeFromString<SerializableVersionCheckResponse?>("""
        |{
        |  "beheerCode": "VERPLICHTE_UPDATE",
        |  "bericht": "Oewe telefoon is oud."
        |}
        """.trimMargin())
        assertNotNull(mandatory)
        assertEquals("VERPLICHTE_UPDATE", mandatory.beheerCode)

        val upToDate = Json.decodeFromString<SerializableVersionCheckResponse?>("")
        assertNull(upToDate)
    }
The last decodeFromString fails with the following message:
Copy code
kotlinx.serialization.json.internal.JsonDecodingException: Expected start of the object '{', but had 'EOF' instead
JSON input:
    at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
	at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:32)
	at kotlinx.serialization.json.internal.JsonLexer.fail(JsonLexer.kt:479)
d
You'll need a custom serialiser.
Actually no, that string isn't even valid JSON.
e
yeah,
decodeFromString("\"\"")
would be valid JSON string that JsonTransformingSerializer could "fix", but an empty body is not JSON. you'll have to handle that class outside of serialization
d
Thanks for the responses. I managed to find a hackish way using a custom KSerializer:
Copy code
object TestSerializer : KSerializer<VersionCheckFetchResponse> {

    override val descriptor: SerialDescriptor = serialDescriptor<VersionCheckFetchResponse>()

    override fun serialize(encoder: Encoder, value: VersionCheckFetchResponse) = TODO()

    override fun deserialize(decoder: Decoder): VersionCheckFetchResponse {
        return try {
            val json = (decoder as? JsonDecoder)?.decodeJsonElement() as JsonObject
            val beheerCode = json["beheerCode"]?.jsonPrimitive?.content
            val bericht = json["bericht"]?.jsonPrimitive?.content
            VersionCheckFetchResponse(beheerCode = beheerCode, bericht = bericht)
        } catch (e: Exception) {
            VersionCheckFetchResponse(null, null)
        }
    }
}
Not liking the solution so I consider this my last resort. ATM I’m looking into response intercepting and altering under specific conditions
e
I think that serializer may cause issues if it's ever used in another structure - silently swallowing exceptions and continuing will allow deserialization to proceed with the decoder in an unexpected state
it would be better to do the fallback outside of kotlinx.serialization entirely, e.g.
Copy code
val value = if (string.isNotEmpty()) {
    json.decodeFromString(Response.serializer(), string)
} else {
    default()
}
or
Copy code
val value = try {
    json.decodeFromString(Response.serializer(), string)
} catch (_: SerializationException) {
    default()
}
d
I agree. Though what I presented was a simplified testcase verifying where the issue was. The actual response is parsed by a ktor wrapped in some company library, so currently I’m trying to intercept and rewrite the response in Kto’s receivePipeline