-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjsontype.go
169 lines (157 loc) · 4.47 KB
/
jsontype.go
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package jsontype
import (
"encoding/json"
"fmt"
"net/mail"
"net/url"
"regexp"
"strings"
"time"
)
const (
// errStringUnmarshal is an error message for when an expected JSON string could not be unmarshalled.
errStringUnmarshal = "failed to unmarshal as a string"
// errUnmarshalPackage is prepended to all unmarshal errors to make troubleshooting easier.
errUnmarshalPackage = "github.com/MicahParks/jsontype JSON unmarshal error"
// errUnreachableFmt is an error message for when code should be unreachable due to an unsupported type.
errUnreachableFmt = "%s: (should be unreachable code in github.com/MicahParks/jsontype) unsupported type: %T"
)
// Options is a set of options for a JSONType. It modifies the behavior of JSON marshal/unmarshal.
type Options struct {
MailAddressAddressOnlyMarshal bool
MailAddressLowerMarshal bool
MailAddressUpperMarshal bool
TimeFormatMarshal string
TimeFormatUnmarshal string
}
// J is a set of common Go types that can be marshaled and unmarshalled with this package.
type J interface {
*mail.Address | *regexp.Regexp | time.Duration | time.Time | *url.URL
}
// JSONType holds a generic J value. It can be used to marshal and unmarshal its value to and from JSON.
type JSONType[T J] struct {
options Options
v T
}
// New creates a new JSONType.
func New[T J](v T) *JSONType[T] {
return &JSONType[T]{
v: v,
}
}
// NewWithOptions creates a new JSONType with options.
func NewWithOptions[T J](v T, options Options) *JSONType[T] {
return &JSONType[T]{
options: options,
v: v,
}
}
// Get returns the held value.
func (j *JSONType[T]) Get() T {
if j == nil {
var t T
return t
}
return j.v
}
// MarshalJSON helps implement the json.Marshaler interface.
func (j *JSONType[T]) MarshalJSON() ([]byte, error) {
var s string
switch v := any(j.Get()).(type) {
case *mail.Address:
if j.options.MailAddressAddressOnlyMarshal {
s = v.Address
} else {
s = v.String()
}
if j.options.MailAddressLowerMarshal {
s = strings.ToLower(s)
} else if j.options.MailAddressUpperMarshal {
s = strings.ToUpper(s)
}
case *regexp.Regexp:
s = v.String()
case time.Duration:
s = v.String()
case time.Time:
format := time.RFC3339
if j.options.TimeFormatMarshal != "" {
format = j.options.TimeFormatMarshal
}
s = v.Format(format)
case *url.URL:
s = v.String()
default:
return nil, fmt.Errorf("%s: (should be unreachable code in github.com/MicahParks/jsontype) unsupported type: %T", errUnmarshalPackage, j.v)
}
return json.Marshal(s)
}
// UnmarshalJSON helps implement the json.Unmarshaler interface.
func (j *JSONType[T]) UnmarshalJSON(bytes []byte) error {
var v any
switch any(j.v).(type) {
case *mail.Address:
var s string
err := json.Unmarshal(bytes, &s)
if err != nil {
return fmt.Errorf("%s: %s: %w", errUnmarshalPackage, errStringUnmarshal, err)
}
addr, err := mail.ParseAddress(s)
if err != nil {
return fmt.Errorf("%s: failed to parse email address: %w", errUnmarshalPackage, err)
}
v = any(addr)
case *regexp.Regexp:
var s string
err := json.Unmarshal(bytes, &s)
if err != nil {
return fmt.Errorf("%s: %s: %w", errStringUnmarshal, errUnmarshalPackage, err)
}
re, err := regexp.Compile(s)
if err != nil {
return fmt.Errorf("%s: failed to compile regexp: %w", errUnmarshalPackage, err)
}
v = any(re)
case time.Duration:
var s string
err := json.Unmarshal(bytes, &s)
if err != nil {
return fmt.Errorf("%s: %s: %w", errUnmarshalPackage, errStringUnmarshal, err)
}
d, err := time.ParseDuration(s)
if err != nil {
return fmt.Errorf("%s: failed to parse duration: %w", errUnmarshalPackage, err)
}
v = any(d)
case time.Time:
var s string
err := json.Unmarshal(bytes, &s)
if err != nil {
return fmt.Errorf("%s: %s: %w", errUnmarshalPackage, errStringUnmarshal, err)
}
format := time.RFC3339
if j.options.TimeFormatUnmarshal != "" {
format = j.options.TimeFormatUnmarshal
}
t, err := time.Parse(format, s)
if err != nil {
return fmt.Errorf("%s: failed to parse time: %w", errUnmarshalPackage, err)
}
v = any(t)
case *url.URL:
var s string
err := json.Unmarshal(bytes, &s)
if err != nil {
return fmt.Errorf("%s: %s: %w", errUnmarshalPackage, errStringUnmarshal, err)
}
u, err := url.Parse(s)
if err != nil {
return fmt.Errorf("%s: failed to parse url: %w", errUnmarshalPackage, err)
}
v = any(u)
default:
return fmt.Errorf(errUnreachableFmt, errUnmarshalPackage, j.v)
}
j.v = v.(T)
return nil
}