Skip to content
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

How to get rid of nullability? #2619

Open
Fleshgrinder opened this issue Mar 30, 2024 · 0 comments
Open

How to get rid of nullability? #2619

Fleshgrinder opened this issue Mar 30, 2024 · 0 comments
Labels

Comments

@Fleshgrinder
Copy link

Fleshgrinder commented Mar 30, 2024

I'm writing Kafka serdes where null has a very special meaning (tombstones). I do want to keep the nullability information for the type parameters, but I don't want the serializer to support null. The problem is that it's not possible to unwrap a serializer that has been wrapped with nullability. As such I'm forced to check every result the serializer or deserializer gives me to ensure it doesn't return null, and fail at runtime. Here's a deserializer example:

@OptIn(ExperimentalSerializationApi::class)
public class JsonDeserializer<T>(
    private val strategy: DeserializationStrategy<T & Any>,
    json: Json? = null,
) : Deserializer<T> {
    private val json = json ?: JSON

    init {
        // We cannot do this, because of the reified factory method.
        // require(!strategy.descriptor.isNullable)
    }

    override fun deserialize(topic: String?, data: ByteArray?): T? {
        if (data == null) {
            return null
        }
        val result = try {
            json.decodeFromStream(strategy, ByteArrayInputStream(data))
        } catch (cause: kotlinx.serialization.SerializationException) {
            throw SerializationException("Kotlinx JSON deserialization failed", cause)
        }
        @Suppress("SENSELESS_COMPARISON")
        if (result == null) {
            throw SerializationException(
                "Kotlinx JSON deserialization returned null which is forbidden as it's not " +
                    "possible anymore to tell the difference between a tombstone and a datum " +
                    "that is null, the actual error is with the producer of the data, since it " +
                    "should never have produced a serialized literal JSON null value in the " +
                    "first place"
            )
        }
        return result
    }
}

public inline fun <reified T> JsonDeserializer(json: Json? = null): JsonDeserializer<T> =
    JsonDeserializer(serializer(), json)

Users who use the constructor with a manually created strategy get a serializer with no nullability, but those who use the reified factory method always get a serializer that supports null. The T & Any on the DeserializationStrategy is ignored entirely by the serializer() call.

JsonDeserializer<String>(serializer())           // 🟢 all good
JsonDeserializer<String?>(serializer<String>())  // 🟡 ok but inconvenient
JsonDeserializer<String>()                       // 🟢 all good
JsonDeserializer<String?()                       // 🔴 not good

Adding an Any bound to T would obviously work, but it would mean that it's not possible anymore to automatically create serdes for topics where the key and/or value is actually nullable (supports tombstones).

I haven't looked into why it's possible that I can pass a KSerializer<T> to a DeserializationStrategy<T & Any> and whether this is actually an issue of this library or a Kotlin issue. But, it's strange…

I have no idea on how to work around this. Obviously I could use reflection and unwrap the inner serializer from the NullableSerializer, but that wouldn't help me with custom serializers that might be created from the serializer() factory method where isNullable is true but no inner serializer exists that could be unwrapped.

I'm not sure if it's even possible to do anything about this here, and I would have used the GitHub discussion feature if available. Anyway, looking for any help, ideas, and insights.

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

No branches or pull requests

1 participant