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