1
2
3
4
5 package json
6
7 import (
8 "errors"
9 "fmt"
10 "io"
11 "reflect"
12 "sort"
13 "strconv"
14 "strings"
15 "unicode"
16 "unicode/utf8"
17 )
18
19 var errIgnoredField = errors.New("ignored field")
20
21 type isZeroer interface {
22 IsZero() bool
23 }
24
25 var isZeroerType = reflect.TypeOf((*isZeroer)(nil)).Elem()
26
27 type structFields struct {
28 flattened []structField
29 byActualName map[string]*structField
30 byFoldedName map[string][]*structField
31 inlinedFallback *structField
32 }
33
34 type structField struct {
35 id int
36 index []int
37 typ reflect.Type
38 fncs *arshaler
39 isZero func(addressableValue) bool
40 isEmpty func(addressableValue) bool
41 fieldOptions
42 }
43
44 func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
45 var fs structFields
46 fs.byActualName = make(map[string]*structField, root.NumField())
47 fs.byFoldedName = make(map[string][]*structField, root.NumField())
48
49
50
51
52
53
54
55 ambiguous := new(structField)
56
57
58 var queueIndex int
59 type queueEntry struct {
60 typ reflect.Type
61 index []int
62 visitChildren bool
63 }
64 queue := []queueEntry{{root, nil, true}}
65 seen := map[reflect.Type]bool{root: true}
66
67
68
69 for queueIndex < len(queue) {
70 qe := queue[queueIndex]
71 queueIndex++
72
73 t := qe.typ
74 inlinedFallbackIndex := -1
75 namesIndex := make(map[string]int)
76 var hasAnyJSONTag bool
77 var hasAnyJSONField bool
78 for i := 0; i < t.NumField(); i++ {
79 sf := t.Field(i)
80 _, hasTag := sf.Tag.Lookup("json")
81 hasAnyJSONTag = hasAnyJSONTag || hasTag
82 options, err := parseFieldOptions(sf)
83 if err != nil {
84 if err == errIgnoredField {
85 continue
86 }
87 return structFields{}, &SemanticError{GoType: t, Err: err}
88 }
89 hasAnyJSONField = true
90 f := structField{
91
92
93
94 index: append(append(make([]int, 0, len(qe.index)+1), qe.index...), i),
95 typ: sf.Type,
96 fieldOptions: options,
97 }
98 if sf.Anonymous && !f.hasName {
99 f.inline = true
100 }
101 if f.inline || f.unknown {
102
103
104
105 if f.inline && f.unknown {
106 err := fmt.Errorf("Go struct field %s cannot have both `inline` and `unknown` specified", sf.Name)
107 return structFields{}, &SemanticError{GoType: t, Err: err}
108 }
109 switch f.fieldOptions {
110 case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true}:
111 case fieldOptions{name: f.name, quotedName: f.quotedName, unknown: true}:
112 default:
113 err := fmt.Errorf("Go struct field %s cannot have any options other than `inline` or `unknown` specified", sf.Name)
114 return structFields{}, &SemanticError{GoType: t, Err: err}
115 }
116
117
118
119 tf := f.typ
120 if tf.Kind() == reflect.Pointer && tf.Name() == "" {
121 tf = tf.Elem()
122 }
123
124
125 if which, _ := implementsWhich(tf,
126 jsonMarshalerV2Type, jsonMarshalerV1Type, textMarshalerType,
127 jsonUnmarshalerV2Type, jsonUnmarshalerV1Type, textUnmarshalerType,
128 ); which != nil && tf != rawValueType {
129 err := fmt.Errorf("inlined Go struct field %s of type %s must not implement JSON marshal or unmarshal methods", sf.Name, tf)
130 return structFields{}, &SemanticError{GoType: t, Err: err}
131 }
132
133
134
135 if tf.Kind() == reflect.Struct {
136 if f.unknown {
137 err := fmt.Errorf("inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a json.RawValue", sf.Name, tf)
138 return structFields{}, &SemanticError{GoType: t, Err: err}
139 }
140 if qe.visitChildren {
141 queue = append(queue, queueEntry{tf, f.index, !seen[tf]})
142 }
143 seen[tf] = true
144 continue
145 }
146
147
148
149 switch {
150 case tf == rawValueType:
151 f.fncs = nil
152 case tf.Kind() == reflect.Map && tf.Key() == stringType:
153 f.fncs = lookupArshaler(tf.Elem())
154 default:
155 err := fmt.Errorf("inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or json.RawValue", sf.Name, tf)
156 return structFields{}, &SemanticError{GoType: t, Err: err}
157 }
158
159
160 if inlinedFallbackIndex >= 0 {
161 err := fmt.Errorf("inlined Go struct fields %s and %s cannot both be a Go map or json.RawValue", t.Field(inlinedFallbackIndex).Name, sf.Name)
162 return structFields{}, &SemanticError{GoType: t, Err: err}
163 }
164 inlinedFallbackIndex = i
165
166
167
168 if fs.inlinedFallback == nil {
169 fs.inlinedFallback = &f
170 } else if len(fs.inlinedFallback.index) == len(f.index) {
171 fs.inlinedFallback = ambiguous
172 }
173 } else {
174
175
176
177
178 switch {
179 case sf.Type.Kind() == reflect.Interface && sf.Type.Implements(isZeroerType):
180 f.isZero = func(va addressableValue) bool {
181
182
183 return va.IsNil() || (va.Elem().Kind() == reflect.Pointer && va.Elem().IsNil()) || va.Interface().(isZeroer).IsZero()
184 }
185 case sf.Type.Kind() == reflect.Pointer && sf.Type.Implements(isZeroerType):
186 f.isZero = func(va addressableValue) bool {
187
188 return va.IsNil() || va.Interface().(isZeroer).IsZero()
189 }
190 case sf.Type.Implements(isZeroerType):
191 f.isZero = func(va addressableValue) bool { return va.Interface().(isZeroer).IsZero() }
192 case reflect.PointerTo(sf.Type).Implements(isZeroerType):
193 f.isZero = func(va addressableValue) bool { return va.Addr().Interface().(isZeroer).IsZero() }
194 }
195
196
197
198 switch sf.Type.Kind() {
199 case reflect.String, reflect.Map, reflect.Array, reflect.Slice:
200 f.isEmpty = func(va addressableValue) bool { return va.Len() == 0 }
201 case reflect.Pointer, reflect.Interface:
202 f.isEmpty = func(va addressableValue) bool { return va.IsNil() }
203 }
204
205 f.id = len(fs.flattened)
206 f.fncs = lookupArshaler(sf.Type)
207 fs.flattened = append(fs.flattened, f)
208
209
210 if !utf8.ValidString(f.name) {
211 err := fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, f.name)
212 return structFields{}, &SemanticError{GoType: t, Err: err}
213 }
214
215 if j, ok := namesIndex[f.name]; ok {
216 err := fmt.Errorf("Go struct fields %s and %s conflict over JSON object name %q", t.Field(j).Name, sf.Name, f.name)
217 return structFields{}, &SemanticError{GoType: t, Err: err}
218 }
219 namesIndex[f.name] = i
220
221
222
223 if f2 := fs.byActualName[f.name]; f2 == nil {
224 fs.byActualName[f.name] = &fs.flattened[len(fs.flattened)-1]
225 } else if len(f2.index) == len(f.index) {
226 fs.byActualName[f.name] = ambiguous
227 }
228 }
229 }
230
231
232
233
234
235
236
237
238
239
240 isEmptyStruct := t.NumField() == 0
241 if !isEmptyStruct && !hasAnyJSONTag && !hasAnyJSONField {
242 err := errors.New("Go struct has no exported fields")
243 return structFields{}, &SemanticError{GoType: t, Err: err}
244 }
245 }
246
247
248
249 var n int
250 for _, f := range fs.flattened {
251 switch f2 := fs.byActualName[f.name]; {
252 case f2 == ambiguous:
253 delete(fs.byActualName, f.name)
254 case f2 == nil:
255 continue
256
257 case reflect.DeepEqual(f.index, f2.index):
258 f.id = n
259 fs.flattened[n] = f
260 fs.byActualName[f.name] = &fs.flattened[n]
261 n++
262 }
263 }
264 fs.flattened = fs.flattened[:n]
265 if fs.inlinedFallback == ambiguous {
266 fs.inlinedFallback = nil
267 }
268 if len(fs.flattened) != len(fs.byActualName) {
269 panic(fmt.Sprintf("BUG: flattened list of fields mismatches fields mapped by name: %d != %d", len(fs.flattened), len(fs.byActualName)))
270 }
271
272
273
274
275 sort.Slice(fs.flattened, func(i, j int) bool {
276 si := fs.flattened[i].index
277 sj := fs.flattened[j].index
278 for len(si) > 0 && len(sj) > 0 {
279 switch {
280 case si[0] < sj[0]:
281 return true
282 case si[0] > sj[0]:
283 return false
284 default:
285 si = si[1:]
286 sj = sj[1:]
287 }
288 }
289 return len(si) < len(sj)
290 })
291
292
293
294 for i, f := range fs.flattened {
295 foldedName := string(foldName([]byte(f.name)))
296 fs.byActualName[f.name] = &fs.flattened[i]
297 fs.byFoldedName[foldedName] = append(fs.byFoldedName[foldedName], &fs.flattened[i])
298 }
299 for foldedName, fields := range fs.byFoldedName {
300 if len(fields) > 1 {
301
302
303 sort.Slice(fields, func(i, j int) bool {
304 return fields[i].id < fields[j].id
305 })
306 fs.byFoldedName[foldedName] = fields
307 }
308 }
309
310 return fs, nil
311 }
312
313 type fieldOptions struct {
314 name string
315 quotedName string
316 hasName bool
317 nocase bool
318 inline bool
319 unknown bool
320 omitzero bool
321 omitempty bool
322 string bool
323 format string
324 }
325
326
327
328
329
330 func parseFieldOptions(sf reflect.StructField) (out fieldOptions, err error) {
331 tag, hasTag := sf.Tag.Lookup("json")
332
333
334 if tag == "-" {
335 return fieldOptions{}, errIgnoredField
336 }
337
338
339 if !sf.IsExported() {
340
341
342
343
344
345 if sf.Anonymous {
346 return fieldOptions{}, fmt.Errorf("embedded Go struct field %s of an unexported type must be explicitly ignored with a `json:\"-\"` tag", sf.Type.Name())
347 }
348
349 if hasTag {
350 return fieldOptions{}, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", sf.Name, tag)
351 }
352 return fieldOptions{}, errIgnoredField
353 }
354
355
356
357
358
359 out.name = sf.Name
360 if len(tag) > 0 && !strings.HasPrefix(tag, ",") {
361
362 n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool {
363 return !strings.ContainsRune(",\\'\"`", r)
364 }))
365 opt := tag[:n]
366 if n == 0 {
367
368 opt, n, err = consumeTagOption(tag)
369 if err != nil {
370 return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err)
371 }
372 }
373 out.hasName = true
374 out.name = opt
375 tag = tag[n:]
376 }
377 b, _ := appendString(nil, out.name, false, nil)
378 out.quotedName = string(b)
379
380
381 var wasFormat bool
382 seenOpts := make(map[string]bool)
383 for len(tag) > 0 {
384
385 if tag[0] != ',' {
386 return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid character %q before next option (expecting ',')", sf.Name, tag[0])
387 }
388 tag = tag[len(","):]
389 if len(tag) == 0 {
390 return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid trailing ',' character", sf.Name)
391 }
392
393
394 opt, n, err := consumeTagOption(tag)
395 if err != nil {
396 return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err)
397 }
398 rawOpt := tag[:n]
399 tag = tag[n:]
400 switch {
401 case wasFormat:
402 return fieldOptions{}, fmt.Errorf("Go struct field %s has `format` tag option that was not specified last", sf.Name)
403 case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "":
404 return fieldOptions{}, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", sf.Name, rawOpt, opt)
405 }
406 switch opt {
407 case "nocase":
408 out.nocase = true
409 case "inline":
410 out.inline = true
411 case "unknown":
412 out.unknown = true
413 case "omitzero":
414 out.omitzero = true
415 case "omitempty":
416 out.omitempty = true
417 case "string":
418 out.string = true
419 case "format":
420 if !strings.HasPrefix(tag, ":") {
421 return fieldOptions{}, fmt.Errorf("Go struct field %s is missing value for `format` tag option", sf.Name)
422 }
423 tag = tag[len(":"):]
424 opt, n, err := consumeTagOption(tag)
425 if err != nil {
426 return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed value for `format` tag option: %v", sf.Name, err)
427 }
428 tag = tag[n:]
429 out.format = opt
430 wasFormat = true
431 default:
432
433
434 normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "")
435 switch normOpt {
436 case "nocase", "inline", "unknown", "omitzero", "omitempty", "string", "format":
437 return fieldOptions{}, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", sf.Name, opt, normOpt)
438 }
439
440
441
442
443 }
444
445
446 if seenOpts[opt] {
447 return fieldOptions{}, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", sf.Name, rawOpt)
448 }
449 seenOpts[opt] = true
450 }
451 return out, nil
452 }
453
454 func consumeTagOption(in string) (string, int, error) {
455 switch r, _ := utf8.DecodeRuneInString(in); {
456
457 case r == '_' || unicode.IsLetter(r):
458 n := len(in) - len(strings.TrimLeftFunc(in, isLetterOrDigit))
459 return in[:n], n, nil
460
461 case r == '\'':
462
463
464
465
466
467
468
469 var inEscape bool
470 b := []byte{'"'}
471 n := len(`'`)
472 for len(in) > n {
473 r, rn := utf8.DecodeRuneInString(in[n:])
474 switch {
475 case inEscape:
476 if r == '\'' {
477 b = b[:len(b)-1]
478 }
479 inEscape = false
480 case r == '\\':
481 inEscape = true
482 case r == '"':
483 b = append(b, '\\')
484 case r == '\'':
485 b = append(b, '"')
486 n += len(`'`)
487 out, err := strconv.Unquote(string(b))
488 if err != nil {
489 return "", 0, fmt.Errorf("invalid single-quoted string: %s", in[:n])
490 }
491 return out, n, nil
492 }
493 b = append(b, in[n:][:rn]...)
494 n += rn
495 }
496 if n > 10 {
497 n = 10
498 }
499 return "", 0, fmt.Errorf("single-quoted string not terminated: %s...", in[:n])
500 case len(in) == 0:
501 return "", 0, io.ErrUnexpectedEOF
502 default:
503 return "", 0, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r)
504 }
505 }
506
507 func isLetterOrDigit(r rune) bool {
508 return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
509 }
510
View as plain text