1
2
3
4
5
6
7 package json
8
9 import (
10 "cmp"
11 "errors"
12 "fmt"
13 "io"
14 "reflect"
15 "slices"
16 "strconv"
17 "strings"
18 "unicode"
19 "unicode/utf8"
20
21 "encoding/json/internal/jsonflags"
22 "encoding/json/internal/jsonwire"
23 )
24
25 type isZeroer interface {
26 IsZero() bool
27 }
28
29 var isZeroerType = reflect.TypeFor[isZeroer]()
30
31 type structFields struct {
32 flattened []structField
33 byActualName map[string]*structField
34 byFoldedName map[string][]*structField
35 embeddedFallback *structField
36
37 errUnsupportedFormat *SemanticError
38 }
39
40
41
42
43
44
45 func (sf *structFields) reindex() {
46 reindex := func(f *structField) {
47 f.index0 = f.index[0]
48 f.index = f.index[1:]
49 if len(f.index) == 0 {
50 f.index = nil
51 }
52 }
53 for i := range sf.flattened {
54 reindex(&sf.flattened[i])
55 }
56 if sf.embeddedFallback != nil {
57 reindex(sf.embeddedFallback)
58 }
59 }
60
61
62
63 func (fs *structFields) lookupByFoldedName(name []byte) []*structField {
64 return fs.byFoldedName[string(foldName(name))]
65 }
66
67 type structField struct {
68 id int
69 index0 int
70 index []int
71 typ reflect.Type
72 fncs *arshaler
73 isZero func(addressableValue) bool
74 isEmpty func(addressableValue) bool
75 fieldOptions
76 }
77
78 var errNoExportedFields = errors.New("Go struct has no exported fields")
79
80 func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError) {
81 orErrorf := func(serr *SemanticError, t reflect.Type, f string, a ...any) *SemanticError {
82 return cmp.Or(serr, &SemanticError{GoType: t, Err: fmt.Errorf(f, a...)})
83 }
84
85
86 var queueIndex int
87 type queueEntry struct {
88 typ reflect.Type
89 index []int
90 visitChildren bool
91 }
92 queue := []queueEntry{{root, nil, true}}
93 seen := map[reflect.Type]bool{root: true}
94
95
96
97 var allFields, embeddedFallbacks []structField
98 for queueIndex < len(queue) {
99 qe := queue[queueIndex]
100 queueIndex++
101
102 t := qe.typ
103 embeddedFallbackIndex := -1
104 namesIndex := make(map[string]int)
105 var hasAnyJSONTag bool
106 var hasAnyJSONField bool
107 for i := range t.NumField() {
108 sf := t.Field(i)
109 _, hasTag := sf.Tag.Lookup("json")
110 hasAnyJSONTag = hasAnyJSONTag || hasTag
111 options, ignored, err := parseFieldOptions(sf)
112 if err != nil {
113 serr = cmp.Or(serr, &SemanticError{GoType: t, Err: err})
114 }
115 if ignored {
116 continue
117 }
118 hasAnyJSONField = true
119 f := structField{
120
121
122
123 index: append(append(make([]int, 0, len(qe.index)+1), qe.index...), i),
124 typ: sf.Type,
125 fieldOptions: options,
126 }
127 if sf.Anonymous && !f.hasName {
128 if indirectType(f.typ).Kind() != reflect.Struct {
129 serr = orErrorf(serr, t, "embedded Go struct field %s of non-struct type must be explicitly given a JSON name", sf.Name)
130 } else {
131 f.embed = true
132 }
133 }
134
135 var handleEmbed, handleField func()
136 handleEmbed = func() {
137
138
139
140 if f.fieldOptions != (fieldOptions{name: f.name, quotedName: f.quotedName, embed: true}) {
141 serr = orErrorf(serr, t, "Go struct field %s cannot have any options other than `embed` specified", sf.Name)
142 if f.hasName {
143 handleField()
144 return
145 }
146 f.fieldOptions = fieldOptions{name: f.name, quotedName: f.quotedName, embed: f.embed}
147 }
148
149
150
151 tf := indirectType(f.typ)
152 if implementsAny(tf, allMethodTypes...) && tf != jsontextValueType {
153 serr = orErrorf(serr, t, "embedded Go struct field %s of type %s must not implement marshal or unmarshal methods", sf.Name, tf)
154 }
155
156
157
158 if tf.Kind() == reflect.Struct {
159 if qe.visitChildren {
160 queue = append(queue, queueEntry{tf, f.index, !seen[tf]})
161 }
162 seen[tf] = true
163 return
164 } else if !sf.IsExported() {
165 serr = orErrorf(serr, t, "embedded Go struct field %s is not exported", sf.Name)
166 return
167 }
168
169
170
171 switch {
172 case tf == jsontextValueType:
173 f.fncs = nil
174 case tf.Kind() == reflect.Map && tf.Key().Kind() == reflect.String:
175 if implementsAny(tf.Key(), allMethodTypes...) {
176 serr = orErrorf(serr, t, "embedded map field %s of type %s must have a string key that does not implement marshal or unmarshal methods", sf.Name, tf)
177 handleField()
178 return
179 }
180 f.fncs = lookupArshaler(tf.Elem())
181 default:
182 serr = orErrorf(serr, t, "embedded Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value", sf.Name, tf)
183 handleField()
184 return
185 }
186
187
188 if embeddedFallbackIndex >= 0 {
189 serr = orErrorf(serr, t, "embedded Go struct fields %s and %s cannot both be a Go map or jsontext.Value", t.Field(embeddedFallbackIndex).Name, sf.Name)
190
191
192 }
193 embeddedFallbackIndex = i
194
195 embeddedFallbacks = append(embeddedFallbacks, f)
196 }
197 handleField = func() {
198
199
200
201
202
203
204 if !sf.IsExported() {
205 tf := indirectType(f.typ)
206 if !(sf.Anonymous && tf.Kind() == reflect.Struct) {
207 serr = orErrorf(serr, t, "Go struct field %s is not exported", sf.Name)
208 return
209 }
210
211
212 if implementsAny(tf, allMethodTypes...) ||
213 (f.omitzero && implementsAny(tf, isZeroerType)) {
214 serr = orErrorf(serr, t, "Go struct field %s is not exported for method calls", sf.Name)
215 return
216 }
217 }
218
219
220 switch {
221 case sf.Type.Kind() == reflect.Interface && sf.Type.Implements(isZeroerType):
222 f.isZero = func(va addressableValue) bool {
223
224
225 return va.IsNil() || (va.Elem().Kind() == reflect.Pointer && va.Elem().IsNil()) || va.Interface().(isZeroer).IsZero()
226 }
227 case sf.Type.Kind() == reflect.Pointer && sf.Type.Implements(isZeroerType):
228 f.isZero = func(va addressableValue) bool {
229
230 return va.IsNil() || va.Interface().(isZeroer).IsZero()
231 }
232 case sf.Type.Implements(isZeroerType):
233 f.isZero = func(va addressableValue) bool { return va.Interface().(isZeroer).IsZero() }
234 case reflect.PointerTo(sf.Type).Implements(isZeroerType):
235 f.isZero = func(va addressableValue) bool { return va.Addr().Interface().(isZeroer).IsZero() }
236 }
237
238
239
240 switch sf.Type.Kind() {
241 case reflect.String, reflect.Map, reflect.Array, reflect.Slice:
242 f.isEmpty = func(va addressableValue) bool { return va.Len() == 0 }
243 case reflect.Pointer, reflect.Interface:
244 f.isEmpty = func(va addressableValue) bool { return va.IsNil() }
245 }
246
247
248 if j, ok := namesIndex[f.name]; ok {
249 serr = orErrorf(serr, t, "Go struct fields %s and %s conflict over JSON object name %q", t.Field(j).Name, sf.Name, f.name)
250
251
252 }
253 namesIndex[f.name] = i
254
255 f.id = len(allFields)
256 f.fncs = lookupArshaler(sf.Type)
257 allFields = append(allFields, f)
258 if f.format != "" && fs.errUnsupportedFormat == nil {
259 fs.errUnsupportedFormat = &SemanticError{GoType: t, Err: fmt.Errorf("Go struct field %s has unsupported `format` tag option", sf.Name)}
260 }
261 }
262
263 if f.embed {
264 handleEmbed()
265 } else {
266 handleField()
267 }
268 }
269
270
271
272
273
274
275
276
277
278
279 isEmptyStruct := t.NumField() == 0
280 if !isEmptyStruct && !hasAnyJSONTag && !hasAnyJSONField {
281 serr = cmp.Or(serr, &SemanticError{GoType: t, Err: errNoExportedFields})
282 }
283 }
284
285
286
287
288
289
290
291
292 flattened := allFields[:0]
293 slices.SortStableFunc(allFields, func(x, y structField) int {
294 return cmp.Or(
295 strings.Compare(x.name, y.name),
296 cmp.Compare(len(x.index), len(y.index)),
297 boolsCompare(!x.hasName, !y.hasName))
298 })
299 for len(allFields) > 0 {
300 n := 1
301 for n < len(allFields) && allFields[n-1].name == allFields[n].name {
302 n++
303 }
304 if n == 1 || len(allFields[0].index) != len(allFields[1].index) || allFields[0].hasName != allFields[1].hasName {
305 flattened = append(flattened, allFields[0])
306 }
307 allFields = allFields[n:]
308 }
309
310
311
312
313 slices.SortFunc(flattened, func(x, y structField) int {
314 return cmp.Compare(x.id, y.id)
315 })
316 for i := range flattened {
317 flattened[i].id = i
318 }
319
320
321
322 slices.SortFunc(flattened, func(x, y structField) int {
323 return slices.Compare(x.index, y.index)
324 })
325
326
327
328 fs = structFields{
329 flattened: flattened,
330 byActualName: make(map[string]*structField, len(flattened)),
331 byFoldedName: make(map[string][]*structField, len(flattened)),
332
333 errUnsupportedFormat: fs.errUnsupportedFormat,
334 }
335 for i, f := range fs.flattened {
336 foldedName := string(foldName([]byte(f.name)))
337 fs.byActualName[f.name] = &fs.flattened[i]
338 fs.byFoldedName[foldedName] = append(fs.byFoldedName[foldedName], &fs.flattened[i])
339 }
340 for foldedName, fields := range fs.byFoldedName {
341 if len(fields) > 1 {
342
343
344 slices.SortFunc(fields, func(x, y *structField) int {
345 return cmp.Compare(x.id, y.id)
346 })
347 fs.byFoldedName[foldedName] = fields
348 }
349 }
350 if n := len(embeddedFallbacks); n == 1 || (n > 1 && len(embeddedFallbacks[0].index) != len(embeddedFallbacks[1].index)) {
351 fs.embeddedFallback = &embeddedFallbacks[0]
352 }
353 fs.reindex()
354 return fs, serr
355 }
356
357
358
359
360 func indirectType(t reflect.Type) reflect.Type {
361 if t.Kind() == reflect.Pointer && t.Name() == "" {
362 t = t.Elem()
363 }
364 return t
365 }
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380 func (f *structField) matchFoldedName(name []byte, flags *jsonflags.Flags) bool {
381 if f.casing == caseIgnore || (flags.Get(jsonflags.MatchCaseInsensitiveNames) && f.casing != caseStrict) {
382 if !flags.Get(jsonflags.MatchCaseSensitiveDelimiter) || strings.EqualFold(string(name), f.name) {
383 return true
384 }
385 }
386 return false
387 }
388
389 const (
390 caseIgnore = 1
391 caseStrict = 2
392 )
393
394 type fieldOptions struct {
395 name string
396 quotedName string
397 hasName bool
398 nameNeedEscape bool
399 casing int8
400 embed bool
401 omitzero bool
402 omitempty bool
403 string bool
404 format string
405 }
406
407
408
409
410 func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool, err error) {
411 tag, hasTag := sf.Tag.Lookup("json")
412
413
414 if tag == "-" {
415 return fieldOptions{}, true, nil
416 }
417
418
419
420
421
422
423
424
425
426
427 if !sf.IsExported() && !sf.Anonymous {
428
429 if hasTag {
430 err = cmp.Or(err, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", sf.Name, tag))
431 }
432 return fieldOptions{}, true, err
433 }
434
435
436 out.name = sf.Name
437 if len(tag) > 0 && !strings.HasPrefix(tag, ",") {
438
439 n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool {
440 return !strings.ContainsRune(",\\'\"`", r)
441 }))
442 name := tag[:n]
443
444
445
446
447 var err2 error
448 if !strings.HasPrefix(tag[n:], ",") && len(name) != len(tag) {
449 name, n, err2 = consumeTagOption(tag, false)
450 if err2 != nil {
451 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
452 }
453 }
454 if !utf8.ValidString(name) {
455 err = cmp.Or(err, fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, name))
456 name = string([]rune(name))
457 }
458 if err2 == nil {
459 out.hasName = true
460 out.name = name
461 }
462 tag = tag[n:]
463 }
464 b, _ := jsonwire.AppendQuote(nil, []byte(out.name), &jsonflags.Flags{})
465 out.quotedName = string(b)
466 out.nameNeedEscape = jsonwire.NeedEscape([]byte(out.name))
467
468
469 var wasFormat bool
470 seenOpts := make(map[string]bool)
471 for len(tag) > 0 {
472
473 if tag[0] != ',' {
474 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid character %q before next option (expecting ',')", sf.Name, tag[0]))
475 } else {
476 tag = tag[len(","):]
477 if len(tag) == 0 {
478 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid trailing ',' character", sf.Name))
479 break
480 }
481 }
482
483
484 opt, n, err2 := consumeTagOption(tag, false)
485 if err2 != nil {
486 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
487 }
488 rawOpt := tag[:n]
489 tag = tag[n:]
490 switch {
491 case wasFormat:
492 err = cmp.Or(err, fmt.Errorf("Go struct field %s has `format` tag option that was not specified last", sf.Name))
493 case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "":
494 err = cmp.Or(err, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", sf.Name, rawOpt, opt))
495 }
496 switch opt {
497 case "case":
498 if !strings.HasPrefix(tag, ":") {
499 err = cmp.Or(err, fmt.Errorf("Go struct field %s is missing value for `case` tag option; specify `case:ignore` or `case:strict` instead", sf.Name))
500 break
501 }
502 tag = tag[len(":"):]
503 opt, n, err2 := consumeTagOption(tag, false)
504 if err2 != nil {
505 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `case` tag option: %v", sf.Name, err2))
506 break
507 }
508 rawOpt := tag[:n]
509 tag = tag[n:]
510 if strings.HasPrefix(rawOpt, "'") {
511 err = cmp.Or(err, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `case:%s` tag option; specify `case:%s` instead", sf.Name, rawOpt, opt))
512 }
513 switch opt {
514 case "ignore":
515 out.casing |= caseIgnore
516 case "strict":
517 out.casing |= caseStrict
518 default:
519 err = cmp.Or(err, fmt.Errorf("Go struct field %s has unknown `case:%s` tag value", sf.Name, rawOpt))
520 }
521 case "embed":
522 out.embed = true
523 case "omitzero":
524 out.omitzero = true
525 case "omitempty":
526 out.omitempty = true
527 case "string":
528 out.string = true
529 case "format":
530 if !strings.HasPrefix(tag, ":") {
531 err = cmp.Or(err, fmt.Errorf("Go struct field %s is missing value for `format` tag option", sf.Name))
532 break
533 }
534 tag = tag[len(":"):]
535 opt, n, err2 := consumeTagOption(tag, true)
536 if err2 != nil {
537 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `format` tag option: %v", sf.Name, err2))
538 break
539 } else if opt == "" {
540 err = cmp.Or(err, fmt.Errorf("Go struct field %s cannot have empty value for `format` tag option", sf.Name))
541 break
542 }
543 tag = tag[n:]
544 out.format = opt
545 wasFormat = true
546 default:
547
548
549 normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "")
550 switch normOpt {
551 case "case", "embed", "omitzero", "omitempty", "string", "format":
552 err = cmp.Or(err, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", sf.Name, opt, normOpt))
553 }
554
555
556
557
558 }
559
560
561 switch {
562 case out.casing == caseIgnore|caseStrict:
563 err = cmp.Or(err, fmt.Errorf("Go struct field %s cannot have both `case:ignore` and `case:strict` tag options", sf.Name))
564 case seenOpts[opt]:
565 err = cmp.Or(err, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", sf.Name, rawOpt))
566 }
567 seenOpts[opt] = true
568 }
569 return out, false, err
570 }
571
572
573
574
575
576 func consumeTagOption(in string, allowQuoted bool) (string, int, error) {
577
578 i := strings.IndexByte(in, ',')
579 if i < 0 {
580 i = len(in)
581 }
582
583 switch r, _ := utf8.DecodeRuneInString(in); {
584
585 case r == '_' || unicode.IsLetter(r):
586 n := len(in) - len(strings.TrimLeftFunc(in, isLetterOrDigit))
587 return in[:n], n, nil
588
589 case r == '\'':
590 if !allowQuoted {
591 return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter)", r)
592 }
593
594
595
596
597
598
599
600
601 var inEscape bool
602 b := []byte{'"'}
603 n := len(`'`)
604 for len(in) > n {
605 r, rn := utf8.DecodeRuneInString(in[n:])
606 switch {
607 case inEscape:
608 if r == '\'' {
609 b = b[:len(b)-1]
610 }
611 inEscape = false
612 case r == '\\':
613 inEscape = true
614 case r == '"':
615 b = append(b, '\\')
616 case r == '\'':
617 b = append(b, '"')
618 n += len(`'`)
619 out, err := strconv.Unquote(string(b))
620 if err != nil {
621 return in[:i], i, fmt.Errorf("invalid single-quoted string: %s", in[:n])
622 }
623 return out, n, nil
624 }
625 b = append(b, in[n:][:rn]...)
626 n += rn
627 }
628 if n > 10 {
629 n = 10
630 }
631 return in[:i], i, fmt.Errorf("single-quoted string not terminated: %s...", in[:n])
632 case len(in) == 0:
633 return in[:i], i, io.ErrUnexpectedEOF
634 default:
635 if !allowQuoted {
636 return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter)", r)
637 }
638 return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r)
639 }
640 }
641
642 func isLetterOrDigit(r rune) bool {
643 return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
644 }
645
646
647 func boolsCompare(x, y bool) int {
648 switch {
649 case !x && y:
650 return -1
651 default:
652 return 0
653 case x && !y:
654 return +1
655 }
656 }
657
View as plain text