-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjson.go
257 lines (232 loc) · 6.33 KB
/
json.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package golib
import (
"bytes"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io"
"net/url"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
// Write the given object into file
func JsonWriteFile(fn string, v interface{}) error {
j, err := json.MarshalIndent(&v, "", " ")
if err != nil {
return err
}
err = os.WriteFile(fn, j, 0644)
return err
}
// Reads the given file into the given value
// If the filename ends in ".gz", it is opened
// using the gzip library.
func JsonReadFile(fn string, v interface{}) error {
var reader io.Reader
fh, err := os.Open(fn)
if err != nil {
return err
}
defer fh.Close()
if strings.HasSuffix(fn, ".gz") {
gzip, err := gzip.NewReader(fh)
if err != nil {
return err
}
defer gzip.Close()
reader = gzip
} else {
reader = fh
}
return JsonUnmarshalReader(reader, v)
}
func JsonUnmarshalReader(r io.Reader, v interface{}) (err error) {
err = json.NewDecoder(r).Decode(&v)
if err != nil {
return fmt.Errorf("json.Decode io.reader error: %w", err)
}
return nil
}
func JsonUnmarshalReaderStrict(r io.Reader, v interface{}) error {
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
err := dec.Decode(&v)
if err != nil {
return fmt.Errorf("json.Decode io.reader error: %w", err)
}
return nil
}
func JsonUnmarshalReadCloser(r io.ReadCloser, v interface{}) error {
defer r.Close()
err := JsonUnmarshalReader(r, v)
if err != nil {
return err
}
return nil
}
func JsonPretty(b []byte) string {
var data interface{}
err := json.Unmarshal(b, &data)
if err != nil {
return fmt.Sprintf("%q [Json Error:%s]", string(b), err)
}
b2, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Sprintf("%q [Json Error:%s]", string(b), err)
}
return string(b2)
}
func JsonBytesIndent(v interface{}, prefix, indent string) ([]byte, error) {
buf := bytes.Buffer{}
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent(prefix, indent)
err := enc.Encode(v)
if err != nil {
return nil, err
}
bs := buf.Bytes()
// remove the \n which Encode adds at the end
return bs[0 : len(bs)-1], nil
}
func JsonBytes(v interface{}) ([]byte, error) {
return JsonBytesIndent(v, "", "")
}
func JsonString(v interface{}) string {
return JsonStringIndent(v, "", " ")
}
func JsonStringIndent(v interface{}, prefix, indent string) string {
if v == nil {
return ""
}
bs, err := JsonBytesIndent(v, prefix, indent)
if err != nil {
return fmt.Sprintf("%#v [Json Error:%s]", v, err)
}
return string(bs)
}
// JsonUnmarshal marshals the source into json and unmarshals it into target.
// If there is an error, it checks for known json parser errors and
// if there is a match, a JsonUnmarshalError with parsed information is returned.
// The error messages are formatted by https://pkg.go.dev/encoding/json#Unmarshal
// * "json: cannot unmarshal <value> into Go value of type <type>"
// * "json: cannot unmarshal <value> into Go struct field <target property name> of type <type>"
func JsonUnmarshal(source []byte, target any) (err error) {
err = json.Unmarshal(source, target)
if err == nil {
return nil
}
regex := regexp.MustCompile(`json: cannot unmarshal ([^\s]+) into Go value of type (.+)$`)
matches := regex.FindStringSubmatch(err.Error())
if len(matches) == 3 {
return NewJsonUnmarshalError(
err,
matches[1], // source type
matches[2], // target type
"", // no target property name
)
}
regex = regexp.MustCompile(`json: cannot unmarshal ([^\s]+) into Go struct field ([^\s]+?) of type (.+)$`)
matches = regex.FindStringSubmatch(err.Error())
if len(matches) == 4 {
return NewJsonUnmarshalError(
err,
matches[1], // source type
matches[3], // target type
matches[2], // target property name
)
}
// no regex match, just return the original error
return err
}
// JsonUnmarshalObject marshals the source into json and unmarshals it into target
func JsonUnmarshalObject(source any, target any) error {
data, err := json.Marshal(source)
if err != nil {
return err
}
return JsonUnmarshal(data, &target)
}
// JsonUnmarshalQuery unmarshals a query string into target. Only the
// first value of each query key is used. The target is a struct where
// the json tags are used to find the query key. The value is parsed
// according to the type of the target struct field.
func JsonUnmarshalQuery(qv url.Values, target any) (err error) {
// Ensure that target is a pointer to a struct
rv := reflect.ValueOf(target)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return errors.New("target must be a non-nil pointer to a struct")
}
rv = rv.Elem()
if rv.Kind() != reflect.Struct {
return errors.New("target must be a pointer to a struct")
}
rt := rv.Type()
sourceData := map[string]any{}
for i := 0; i < rv.NumField(); i++ {
// fieldValue := rv.Field(i)
fInfo := rt.Field(i)
if !fInfo.IsExported() {
continue
}
parts := strings.Split(fInfo.Tag.Get("json"), ",")
var fieldName string
switch len(parts) {
case 0:
fieldName = fInfo.Name
default:
fieldName = parts[0]
}
switch fieldName {
case "-":
// skip this
continue
case "":
fieldName = fInfo.Name
default:
// already set
}
v := qv.Get(fieldName)
if v == "" {
continue
}
fType := fInfo.Type
for fType.Kind() == reflect.Pointer {
fType = fType.Elem()
}
// Pln("field %q type %q kind %q name %q value %q", fInfo.Name, fInfo.Type, fType.Kind(), fieldName, v)
switch fType.Kind() {
case reflect.String:
sourceData[fieldName] = v
case reflect.Int, reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64:
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return fmt.Errorf("JsonUnmarshalQuery: value %q for field %q is not %s", v, fInfo.Name, fInfo.Type.Kind())
}
sourceData[fieldName] = i
case reflect.Float64, reflect.Float32:
fl, err := strconv.ParseFloat(v, 64)
if err != nil {
return fmt.Errorf("JsonUnmarshalQuery: value %q for field %q is not %s", v, fInfo.Name, fInfo.Type.Kind())
}
sourceData[fieldName] = fl
case reflect.Slice, reflect.Interface:
// value must be JSON
sourceData[fieldName] = json.RawMessage([]byte(v))
case reflect.Bool:
sourceData[fieldName] = GetBool(v)
default:
// assume JSON
sourceData[fieldName] = json.RawMessage([]byte(v))
}
}
err = JsonUnmarshalObject(sourceData, target)
if err != nil {
return fmt.Errorf("JsonUnmarshalQuery: %w", err)
}
return nil
}