question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Handle empty String as null

See original GitHub issue

What is your use-case and why do you need this feature? Unfortunately, not every API sends valid JSON. Sometimes, it sends an empty string instead null. To use the nullable type, I use a workaround based on the private serialized string and typed getter/setter. A custom serializer for classes, containing many members, is more complex than this workaround.

It would be nice, if the serialization framework could handle this workaround, as a custom setting.

JsonConfiguration(handleEmptyStringAsNull = true The default value should be false, to enforce strict mode.

Describe the solution you’d like

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull

class EmptyStringSerializerTest {
    private val json = Json(JsonConfiguration.Stable)

    /**
     * Normal, expected usage
     */
    @Serializable
    data class Valid(var content: Long?)

    @Test
    fun expected() {
        val json = Json(JsonConfiguration(handleEmptyStringAsNull = true)) //TODO: New setting to convert empty strings to nullable, instead throwing, default = false (strict)
        val invalideString = """{"content":""}"""
        val failing = json.parse(Valid.serializer(), invalideString) // java.lang.NumberFormatException: For input string: ""
        assertNull(failing.content)
    }

    /**
     * Workaround to eliminate the usage of a custom serializer, which is not nice with a class containing many members
     */
    @Serializable
    data class Invalide(@SerialName("content") private var value: String) {
        var content: Long? get() = value.toLongOrNull()
            set(newValue) {
                this.value = (newValue ?: "").toString()
            }
    }

    /**
     * Just testing the workaround
     */
    @Test
    fun customGetterSetterUsedByInvalide() {
        val invalide = Invalide("foo")
        invalide.content = null
        val empty = json.stringify(Invalide.serializer(), invalide)
        assertEquals("""{"content":""}""", empty)
        val invalideParsed = json.parse(Invalide.serializer(), empty)
        assertNull(invalideParsed.content)

        invalide.content = 42
        val contentString = json.stringify(Invalide.serializer(), invalide)
        assertEquals("""{"content":"42"}""", contentString)
        val contentSet = json.parse(Invalide.serializer(), contentString)
        assertEquals(invalide, contentSet)
    }


    /**
     * Working test of the valid, normal class
     */
    @Test
    fun valid() {
        val valid = Valid(null)
        val empty = json.stringify(Valid.serializer(), valid)
        assertEquals("""{"content":null}""", empty)
        val validParsed = json.parse(Valid.serializer(), empty)
        assertNull(validParsed.content)

        valid.content = 42
        val contentLong = json.stringify(Valid.serializer(), valid)
        assertEquals("""{"content":42}""", contentLong)
        val contentSet = json.parse(Valid.serializer(), contentLong)
        assertEquals(valid, contentSet)
    }
}

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:14 (10 by maintainers)

github_iconTop GitHub Comments

2reactions
hfhbdcommented, Mar 2, 2020

The api sends "" instead null for every type, not only Strings. So your NullableStringSerializer has to be generic, which would be impossible…

class NullableStringSerializer<In : Any>(private val delegate: KSerializer<In>) : KSerializer<In?> {

    override fun serialize(encoder: Encoder, obj: In?) {
        delegate.nullable.serialize(encoder, obj)
    }

    override fun deserialize(decoder: Decoder): In? {
        val jsonStringOfIn = decoder.decodeString() //wrong methods, does not return a object as string
        if (jsonStringOfIn.isEmpty()) return null
        return delegate.nullable.parse(jsonStringOfIn) // there is no fun parse(String): T in KSerializer
    }

    override val descriptor = StringDescriptor.withName("NullableSerializerOf${delegate.descriptor}")
}

How to call this in @Serializeable(with = NullableStringSerializer<In>::class)?

1reaction
DRSchlaubicommented, May 9, 2021

Another Problem is that e.g. the Discord API sometimes just returns an empty string as the entire response and currently there is no way of parsing that response properly as the KSerializer API doesn’t give you the information whether the input actually contains any content or not.

The way we solved this before is just catching the error any decode method would throw when trying to decode an empty string, but this now causes the lexer.expectEof() line to fail after upgrading to 1.2.0

java.lang.StringIndexOutOfBoundsException: String index out of range: -1
	at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)
	at java.base/java.lang.String.charAt(String.java:711)
	at kotlinx.serialization.json.internal.JsonLexer.consumeNextToken(JsonLexer.kt:234)
	at kotlinx.serialization.json.internal.JsonLexer.expectEof(JsonLexer.kt:128)
	at kotlinx.serialization.json.Json.decodeFromString(Json.kt:96)
Read more comments on GitHub >

github_iconTop Results From Across the Web

Get empty string when null - java
Returns either the passed in String, or if the String is null, an empty String (""). If you also want to get rid...
Read more >
21.2 Validating Null and Empty Strings
An empty string is represented as "" . It is a character sequence of zero characters. A null string is represented by null...
Read more >
Java: Check if String is Null, Empty or Blank
The isEmpty() method returns true or false depending on whether or not our string contains any text. It's easily chainable with a string...
Read more >
When to Use NULL and When to Use Empty String
A string refers to a character's sequence. Sometimes strings can be empty or NULL. The difference is that NULL is used to refer...
Read more >
Handle empty strings when migrating from Oracle to ...
However, because an empty string is another way of representing NULL in Oracle, no difference is observed when NULL handling functions are ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found