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
408
409 if tag == "-" {
410 return fieldOptions{}, true, nil
411 }
412
413
414
415
416
417
418
419
420
421
422 if !sf.IsExported() && !sf.Anonymous {
423
424 if hasTag {
425 err = cmp.Or(err, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", sf.Name, tag))
426 }
427 return fieldOptions{}, true, err
428 }
429
430
431
432
433
434 out.name = sf.Name
435 if len(tag) > 0 && !strings.HasPrefix(tag, ",") {
436
437 n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool {
438 return !strings.ContainsRune(",\\'\"`", r)
439 }))
440 name := tag[:n]
441
442
443
444
445 var err2 error
446 if !strings.HasPrefix(tag[n:], ",") && len(name) != len(tag) {
447 name, n, err2 = consumeTagOption(tag)
448 if err2 != nil {
449 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
450 }
451 }
452 if !utf8.ValidString(name) {
453 err = cmp.Or(err, fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, name))
454 name = string([]rune(name))
455 }
456 if err2 == nil {
457 out.hasName = true
458 out.name = name
459 }
460 tag = tag[n:]
461 }
462 b, _ := jsonwire.AppendQuote(nil, out.name, &jsonflags.Flags{})
463 out.quotedName = string(b)
464 out.nameNeedEscape = jsonwire.NeedEscape(out.name)
465
466
467 var wasFormat bool
468 seenOpts := make(map[string]bool)
469 for len(tag) > 0 {
470
471 if tag[0] != ',' {
472 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]))
473 } else {
474 tag = tag[len(","):]
475 if len(tag) == 0 {
476 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid trailing ',' character", sf.Name))
477 break
478 }
479 }
480
481
482 opt, n, err2 := consumeTagOption(tag)
483 if err2 != nil {
484 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
485 }
486 rawOpt := tag[:n]
487 tag = tag[n:]
488 switch {
489 case wasFormat:
490 err = cmp.Or(err, fmt.Errorf("Go struct field %s has `format` tag option that was not specified last", sf.Name))
491 case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "":
492 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))
493 }
494 switch opt {
495 case "case":
496 if !strings.HasPrefix(tag, ":") {
497 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))
498 break
499 }
500 tag = tag[len(":"):]
501 opt, n, err2 := consumeTagOption(tag)
502 if err2 != nil {
503 err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `case` tag option: %v", sf.Name, err2))
504 break
505 }
506 rawOpt := tag[:n]
507 tag = tag[n:]
508 if strings.HasPrefix(rawOpt, "'") {
509 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))
510 }
511 switch opt {
512 case "ignore":
513 out.casing |= caseIgnore
514 case "strict":
515 out.casing |= caseStrict
516 default:
517 err = cmp.Or(err, fmt.Errorf("Go struct field %s has unknown `case:%s` tag value", sf.Name, rawOpt))
518 }
519 case "inline":
520 out.inline = true
521 case "unknown":
522 out.unknown = 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)
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 }
540 tag = tag[n:]
541 out.format = opt
542 wasFormat = true
543 default:
544
545
546 normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "")
547 switch normOpt {
548 case "case", "inline", "unknown", "omitzero", "omitempty", "string", "format":
549 err = cmp.Or(err, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", sf.Name, opt, normOpt))
550 }
551
552
553
554
555 }
556
557
558 switch {
559 case out.casing == caseIgnore|caseStrict:
560 err = cmp.Or(err, fmt.Errorf("Go struct field %s cannot have both `case:ignore` and `case:strict` tag options", sf.Name))
561 case seenOpts[opt]:
562 err = cmp.Or(err, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", sf.Name, rawOpt))
563 }
564 seenOpts[opt] = true
565 }
566 return out, false, err
567 }
568
569
570
571
572
573 func consumeTagOption(in string) (string, int, error) {
574
575 i := strings.IndexByte(in, ',')
576 if i < 0 {
577 i = len(in)
578 }
579
580 switch r, _ := utf8.DecodeRuneInString(in); {
581
582 case r == '_' || unicode.IsLetter(r):
583 n := len(in) - len(strings.TrimLeftFunc(in, isLetterOrDigit))
584 return in[:n], n, nil
585
586 case r == '\'':
587
588
589
590
591
592
593
594 var inEscape bool
595 b := []byte{'"'}
596 n := len(`'`)
597 for len(in) > n {
598 r, rn := utf8.DecodeRuneInString(in[n:])
599 switch {
600 case inEscape:
601 if r == '\'' {
602 b = b[:len(b)-1]
603 }
604 inEscape = false
605 case r == '\\':
606 inEscape = true
607 case r == '"':
608 b = append(b, '\\')
609 case r == '\'':
610 b = append(b, '"')
611 n += len(`'`)
612 out, err := strconv.Unquote(string(b))
613 if err != nil {
614 return in[:i], i, fmt.Errorf("invalid single-quoted string: %s", in[:n])
615 }
616 return out, n, nil
617 }
618 b = append(b, in[n:][:rn]...)
619 n += rn
620 }
621 if n > 10 {
622 n = 10
623 }
624 return in[:i], i, fmt.Errorf("single-quoted string not terminated: %s...", in[:n])
625 case len(in) == 0:
626 return in[:i], i, io.ErrUnexpectedEOF
627 default:
628 return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r)
629 }
630 }
631
632 func isLetterOrDigit(r rune) bool {
633 return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
634 }
635
636
637 func boolsCompare(x, y bool) int {
638 switch {
639 case !x && y:
640 return -1
641 default:
642 return 0
643 case x && !y:
644 return +1
645 }
646 }
647
View as plain text