diff --git a/sdk/nullable/nullable.go b/sdk/nullable/nullable.go new file mode 100644 index 00000000000..8e618da4eb1 --- /dev/null +++ b/sdk/nullable/nullable.go @@ -0,0 +1,103 @@ +package nullable + +import ( + "bytes" + "encoding/json" +) + +type Type[T comparable] map[bool]T + +func Value[T comparable](t T) Type[T] { + var n Type[T] + n.Set(t) + return n +} + +func NoZero[T comparable](t T) Type[T] { + var n Type[T] + n.SetNoZero(t) + return n +} + +// Get retrieves the underlying value, if present, and returns nil if the value is null +func (t Type[T]) Get() *T { + var empty T + if t.IsNull() { + return nil + } + if t.IsSpecified() { + ret := t[true] + return &ret + } + return &empty +} + +// GetOrZero retrieves the underlying value, if present, and returns the zero value if null +func (t Type[T]) GetOrZero() T { + var empty T + val := t.Get() + if val == nil { + return empty + } + return *val +} + +// IsNull indicate whether the field was sent, and had a value of `null` +func (t Type[T]) IsNull() bool { + _, foundNull := t[false] + return foundNull +} + +// IsSpecified indicates whether the field was sent +func (t Type[T]) IsSpecified() bool { + return len(t) != 0 +} + +// Set sets the underlying value to a given value +func (t *Type[T]) Set(value T) { + *t = map[bool]T{true: value} +} + +// SetNoZero sets the underlying value to a given value, whilst also nulling the field if it was set to +// its zero value. This ensures that zero values are sent as null. +func (t *Type[T]) SetNoZero(value T) { + var empty T + *t = map[bool]T{value != empty: value} +} + +// SetNull indicate that the field was sent, and had a value of `null` +func (t *Type[T]) SetNull() { + var empty T + *t = map[bool]T{false: empty} +} + +// SetUnspecified indicate whether the field was sent +func (t *Type[T]) SetUnspecified() { + *t = map[bool]T{} +} + +func (t Type[T]) MarshalJSON() ([]byte, error) { + // note: if value was unspecified, and `omitempty` is set on the field tags, `json.Marshal` will omit this field + // if value was specified, and `null`, marshal it + if t.IsNull() { + return []byte("null"), nil + } + // otherwise, we have a value, so marshal it + return json.Marshal(t[true]) +} + +func (t *Type[T]) UnmarshalJSON(data []byte) error { + // note: if value is unspecified, UnmarshalJSON won't be called + // if value is specified and `null` + if bytes.Equal(data, []byte("null")) { + t.SetNull() + return nil + } + // otherwise, we have an actual value, so parse it + var val T + if err := json.Unmarshal(data, &val); err != nil { + return err + } + t.Set(val) + return nil +}