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