option.go

  1package param
  2
  3import (
  4	"encoding/json"
  5	"reflect"
  6	"time"
  7)
  8
  9func NewOpt[T comparable](v T) Opt[T] {
 10	return Opt[T]{Value: v, Status: included}
 11}
 12
 13// Sets an optional field to null, to set an object to null use [NullObj].
 14func NullOpt[T comparable]() Opt[T] { return Opt[T]{Status: null} }
 15
 16type Opt[T comparable] struct {
 17	Value T
 18	// indicates whether the field should be omitted, null, or valid
 19	Status Status
 20}
 21
 22type Status int8
 23
 24const (
 25	omitted Status = iota
 26	null
 27	included
 28)
 29
 30type Optional interface {
 31	// IsPresent returns true if the value is not "null" or omitted
 32	IsPresent() bool
 33
 34	// IsOmitted returns true if the value is omitted, it returns false if the value is "null".
 35	IsOmitted() bool
 36
 37	// IsNull returns true if the value is "null", it returns false if the value is omitted.
 38	IsNull() bool
 39}
 40
 41// IsPresent returns true if the value is not "null" and not omitted
 42func (o Opt[T]) IsPresent() bool {
 43	var empty Opt[T]
 44	return o.Status == included || o != empty && o.Status != null
 45}
 46
 47// IsNull returns true if the value is specifically the JSON value "null".
 48// It returns false if the value is omitted.
 49//
 50// Prefer to use [IsPresent] to check the presence of a value.
 51func (o Opt[T]) IsNull() bool { return o.Status == null }
 52
 53// IsOmitted returns true if the value is omitted.
 54// It returns false if the value is the JSON value "null".
 55//
 56// Prefer to use [IsPresent] to check the presence of a value.
 57func (o Opt[T]) IsOmitted() bool { return o == Opt[T]{} }
 58
 59func (o Opt[T]) MarshalJSON() ([]byte, error) {
 60	if !o.IsPresent() {
 61		return []byte("null"), nil
 62	}
 63	return json.Marshal(o.Value)
 64}
 65
 66func (o *Opt[T]) UnmarshalJSON(data []byte) error {
 67	if string(data) == "null" {
 68		o.Status = null
 69		return nil
 70	}
 71	return json.Unmarshal(data, &o.Value)
 72}
 73
 74func (o Opt[T]) Or(v T) T {
 75	if o.IsPresent() {
 76		return o.Value
 77	}
 78	return v
 79}
 80
 81// This is a sketchy way to implement time Formatting
 82var timeType = reflect.TypeOf(time.Time{})
 83var timeTimeValueLoc, _ = reflect.TypeOf(Opt[time.Time]{}).FieldByName("Value")
 84
 85// Don't worry about this function, returns nil to fallback towards [MarshalJSON]
 86func (o Opt[T]) MarshalJSONWithTimeLayout(format string) []byte {
 87	t, ok := any(o.Value).(time.Time)
 88	if !ok || o.IsNull() {
 89		return nil
 90	}
 91
 92	if format == "" {
 93		format = time.RFC3339
 94	} else if format == "date" {
 95		format = "2006-01-02"
 96	}
 97
 98	b, err := json.Marshal(t.Format(format))
 99	if err != nil {
100		return nil
101	}
102	return b
103}