Skip to content

Optionals

Differentiate between missing and null values.

RFC 7396 (JSON Merge Patch) specifies:

  • Missing properties in JSON PATCH requests should be interpreted as “preserve the existing value”.
  • null properties in JSON PATCH requests should be interpreted as “remove this property”.

In order to implement this, we must have some way to differentiate between missing and null properties. Traditional serialization libraries like kotlinx.serialization and Jackson don’t provide this out of the box. Kairo provides Optional<T>.

json.deserialize<Optional>("{}")
// => Optional.Missing
json.deserialize<Optional>("""{"value":null}""")
// => Optional.Null
json.deserialize<Optional>("""{"value":"some value"}""")
// => Optional.Value("some value")

Install kairo-optional.

build.gradle.kts
dependencies {
implementation("com.highbeam.kairo:kairo-optional")
}

There are two important points to note when using Optional<T>.

First, you must add OptionalModule to your KairoJson instance.

val json: KairoJson =
KairoJson {
addModule(OptionalModule())
}

Second, you must add the @JsonInclude(JsonInclude.Include.NON_ABSENT) annotation to the class or property. Without this, Optional.Missing will serialize as null instead of being omitted.

@JsonInclude(JsonInclude.Include.NON_ABSENT)
data class Update(
val value: Optional<String> = Optional.Missing,
)

Use ifSpecified to act only when a value is present (not Missing).

update.name.ifSpecified { name ->
// name is String? here (null if Optional.Null).
entity.name = name
}

Use transform to map the inner value while preserving the Missing state.

val upperName: Optional<String> = update.name.transform { it?.uppercase() }

Use fromNullable to convert a nullable value into an Optional.

Optional.fromNullable("hello") // => Optional.Value("hello")
Optional.fromNullable(null) // => Optional.Null

Required<T> is similar to Optional<T> but does not allow null values. It has two states: Missing and Value. Use Required<T> when a field can be omitted but should never be null.

@JsonInclude(JsonInclude.Include.NON_ABSENT)
data class Update(
val name: Required<String> = Required.Missing,
)