Nullable Values
The official smithy toolset does not offer anything to distinguish between an absence of value and a value set to null during (de) serialisation.
In order to differentiate the two, for example in order to allow a HTTP server to implement merge patch semantics or return explicit null rather than silently dropping a field, you can use the alloy.nullable
trait on members of a structure shape. For example:
namespace example
use alloy#nullable
structure Foo {
@nullable
a: Integer
}
This will be rendered as
package example
import smithy4s._
import smithy4s.schema.Schema._
final case class Foo(a: Option[Nullable[Int]] = None)
object Foo extends ShapeTag.Companion[Foo] {
val id: ShapeId = ShapeId("example", "FooIsh")
val hints: Hints = Hints.empty
implicit val schema: Schema[Foo] = struct(
int.nullable.optional[Foo]("a", _.a),
){
Foo.apply
}.withId(id).addHints(hints)
}
The type Nullable[A]
is an ADT with two members: Null
and Value(a)
for some a
. This makes it exactly equivalent to Option[A]
, and methods Nullable.fromOption(option)
and nullable.toOption
exist to allow for easy conversion between the two types. Nullable
is used rather than Option
for having clear semantics.
In this example, Foo(Some(Nullable.Null))
corresponds to an explicit value of null
while Foo(None)
corresponds to absence of a value. This applies to both serialization and deserialization.
Combinations with other annotations
The annotation @nullable
can be combined with both @required
and @default
, with the following effects:
- annotating as
@required
will forbid the field from being omitted but permit null to be passed explicitly on deserialization. It will always include the field but potentially set it to null on serialization. - annotating as
@default
works the same as default values for non-nullable fields, with the exception that the default can be set to null and not automatically adjusted into a "zero value"
In both cases, the resulting Scala type of the field will be smithy.Nullable[T]
.