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}