arrow-kt / arrow-integrations

Λrrow Integrations is part of Λrrow, a functional companion to Kotlin's Standard Library
http://arrow-kt.io
Other
27 stars 6 forks source link

["Request"] OptionSerializationConverter should remove null when None #46

Closed frecco75 closed 3 years ago

frecco75 commented 3 years ago

What version are you currently using? I am using version 0.10.5

What would you like to see? I would like to remove "null" in json when None is provided.

Hello, I think it will be nice to remove null from json serialization. I can't build the project locally because of build properties referencing old arrow-kt config files, but this is a solution:

Define an OptionSerializer:

private object OptionSerializer : StdDelegatingSerializer(OptionSerializationConverter) {
    override fun isEmpty(prov: SerializerProvider?, value: Any?) = None == value || super.isEmpty(prov, value)

    override fun withDelegate(converter: Converter<Any, *>?, delegateType: JavaType?, delegateSerializer: JsonSerializer<*>?) = this
}

and references it in init block addSerializer(Option::class.java, OptionSerializer).

With this, an object mapper configured withsetSerializationInclusion(NON_EMPTY) won't have "null" in json when Option is None.

myuwono commented 3 years ago

@frecco75 hi! thanks a lot for this. normally the way to disambiguate a none in Option<T> (e.g. java8 Optional<T>) vs an empty List<T> in jackson is to use non absent inclusion. https://fasterxml.github.io/jackson-annotations/javadoc/2.7/com/fasterxml/jackson/annotation/JsonInclude.Include.html#NON_ABSENT

what do you think if we pursue this instead?

frecco75 commented 3 years ago

You are right, NON_ABSENT seem to be pertinent for this use case. But NON_EMPTY should work too, if you look the documentation :

public static final JsonInclude.Include NON_EMPTY Value that indicates that only properties with null value, or what is considered empty, are not to be included. Definition of emptiness is data type specific; see below for details on actual handling. Default emptiness for all types includes:

Null values. "Absent" values (see NON_ABSENT) so that as baseline, "empty" set includes values that would be excluded by both NON_NULL and NON_ABSENT. ...

myuwono commented 3 years ago

@frecco75 that's right. We've addressed this in #52 and that serializer now support both inclusion strategies. Indeed both should work. However, i'd definitely encourage to opt for NON_ABSENT because serialization of Option<T> with NON_EMPTY inclusion strategy may not round trip in some cases.

import arrow.core.Option
import arrow.core.none
import arrow.core.some
import arrow.integrations.jackson.module.registerArrowModule
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule

val mapper = ObjectMapper()
    .registerKotlinModule()
    .registerArrowModule()
    .setSerializationInclusion(JsonInclude.Include.NON_EMPTY)

data class Foo(val value: Option<String>)

mapper.writeValueAsString(Foo(none())) // {}
mapper.writeValueAsString(Foo("foo".some())) // {"value":"foo"}

// does not round trip
val encoded = mapper.writeValueAsString(Foo("".some())) // {}
mapper.readValue(encoded, Foo::class.java) // Foo(value=None)