-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnullable.go
More file actions
74 lines (64 loc) · 1.6 KB
/
nullable.go
File metadata and controls
74 lines (64 loc) · 1.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package main
import (
"bytes"
"encoding/json"
"errors"
)
// Nullable implements a field that can be null and/or optional, meant for JSON serialization / deserialization.
//
// Internal impl detail:
// - map[true]T means a value was provided
// - map[false]T means an explicit null was provided
// - nil or zero map means the field was not provided
type Nullable[T any] map[bool]T
func (t Nullable[T]) Get() (T, error) {
var empty T
if t.IsNull() {
return empty, errors.New("value is null")
}
if !t.IsSpecified() {
return empty, errors.New("value is not specified")
}
return t[true], nil
}
func (t *Nullable[T]) Set(value T) {
*t = map[bool]T{true: value}
}
func (t Nullable[T]) IsNull() bool {
_, foundNull := t[false]
return foundNull
}
func (t *Nullable[T]) SetNull() {
var empty T
*t = map[bool]T{false: empty}
}
func (t Nullable[T]) IsSpecified() bool {
return len(t) != 0
}
func (t *Nullable[T]) SetUnspecified() {
*t = map[bool]T{}
}
func (t Nullable[T]) MarshalJSON() ([]byte, error) {
// case null: explicitly serialize as "null"
if t.IsNull() {
return []byte("null"), nil
}
// case not specified and omitempty: will be ommited
// case of an actual value: marshal that value
return json.Marshal(t[true])
}
func (t *Nullable[T]) UnmarshalJSON(data []byte) error {
// - case not provided: UnmarshalJSON will not even be called
// - case of an explicit null: check for that data explicitly
if bytes.Equal(data, []byte("null")) {
t.SetNull()
return nil
}
// - case of parsing an actual value
var v T
if err := json.Unmarshal(data, &v); err != nil {
return err
}
t.Set(v)
return nil
}