1
2
3
4
5 package json
6
7 import (
8 "encoding"
9 "errors"
10 "reflect"
11 "testing"
12 )
13
14 type unexported struct{}
15
16 func TestMakeStructFields(t *testing.T) {
17 type Embed struct {
18 Foo string
19 }
20 type Recursive struct {
21 A string
22 *Recursive `json:",inline"`
23 B string
24 }
25 type MapStringAny map[string]any
26 tests := []struct {
27 name testName
28 in any
29 want structFields
30 wantErr error
31 }{{
32 name: name("Names"),
33 in: struct {
34 F1 string
35 F2 string `json:"-"`
36 F3 string `json:"json_name"`
37 f3 string
38 F5 string `json:"json_name_nocase,nocase"`
39 }{},
40 want: structFields{
41 flattened: []structField{
42 {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{name: "F1", quotedName: `"F1"`}},
43 {id: 1, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "json_name", quotedName: `"json_name"`, hasName: true}},
44 {id: 2, index: []int{4}, typ: stringType, fieldOptions: fieldOptions{name: "json_name_nocase", quotedName: `"json_name_nocase"`, hasName: true, nocase: true}},
45 },
46 },
47 }, {
48 name: name("BreadthFirstSearch"),
49 in: struct {
50 L1A string
51 L1B struct {
52 L2A string
53 L2B struct {
54 L3A string
55 } `json:",inline"`
56 L2C string
57 } `json:",inline"`
58 L1C string
59 L1D struct {
60 L2D string
61 L2E struct {
62 L3B string
63 } `json:",inline"`
64 L2F string
65 } `json:",inline"`
66 L1E string
67 }{},
68 want: structFields{
69 flattened: []structField{
70 {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{name: "L1A", quotedName: `"L1A"`}},
71 {id: 3, index: []int{1, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L2A", quotedName: `"L2A"`}},
72 {id: 7, index: []int{1, 1, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L3A", quotedName: `"L3A"`}},
73 {id: 4, index: []int{1, 2}, typ: stringType, fieldOptions: fieldOptions{name: "L2C", quotedName: `"L2C"`}},
74 {id: 1, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "L1C", quotedName: `"L1C"`}},
75 {id: 5, index: []int{3, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L2D", quotedName: `"L2D"`}},
76 {id: 8, index: []int{3, 1, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L3B", quotedName: `"L3B"`}},
77 {id: 6, index: []int{3, 2}, typ: stringType, fieldOptions: fieldOptions{name: "L2F", quotedName: `"L2F"`}},
78 {id: 2, index: []int{4}, typ: stringType, fieldOptions: fieldOptions{name: "L1E", quotedName: `"L1E"`}},
79 },
80 },
81 }, {
82 name: name("NameResolution"),
83 in: struct {
84 X1 struct {
85 X struct {
86 A string
87 B string
88 D string
89 } `json:",inline"`
90 } `json:",inline"`
91 X2 struct {
92 X struct {
93 B string
94 C string
95 D string
96 } `json:",inline"`
97 } `json:",inline"`
98 A string
99 D string
100 }{},
101 want: structFields{
102 flattened: []structField{
103 {id: 2, index: []int{1, 0, 1}, typ: stringType, fieldOptions: fieldOptions{name: "C", quotedName: `"C"`}},
104 {id: 0, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "A", quotedName: `"A"`}},
105 {id: 1, index: []int{3}, typ: stringType, fieldOptions: fieldOptions{name: "D", quotedName: `"D"`}},
106 },
107 },
108 }, {
109 name: name("Embed/Implicit"),
110 in: struct {
111 Embed
112 }{},
113 want: structFields{
114 flattened: []structField{
115 {id: 0, index: []int{0, 0}, typ: stringType, fieldOptions: fieldOptions{name: "Foo", quotedName: `"Foo"`}},
116 },
117 },
118 }, {
119 name: name("Embed/Explicit"),
120 in: struct {
121 Embed `json:",inline"`
122 }{},
123 want: structFields{
124 flattened: []structField{
125 {id: 0, index: []int{0, 0}, typ: stringType, fieldOptions: fieldOptions{name: "Foo", quotedName: `"Foo"`}},
126 },
127 },
128 }, {
129 name: name("Recursive"),
130 in: struct {
131 A string
132 Recursive `json:",inline"`
133 C string
134 }{},
135 want: structFields{
136 flattened: []structField{
137 {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{name: "A", quotedName: `"A"`}},
138 {id: 2, index: []int{1, 2}, typ: stringType, fieldOptions: fieldOptions{name: "B", quotedName: `"B"`}},
139 {id: 1, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "C", quotedName: `"C"`}},
140 },
141 },
142 }, {
143 name: name("InlinedFallback/Cancelation"),
144 in: struct {
145 X1 struct {
146 X RawValue `json:",inline"`
147 } `json:",inline"`
148 X2 struct {
149 X map[string]any `json:",unknown"`
150 } `json:",inline"`
151 }{},
152 want: structFields{},
153 }, {
154 name: name("InlinedFallback/Precedence"),
155 in: struct {
156 X1 struct {
157 X RawValue `json:",inline"`
158 } `json:",inline"`
159 X2 struct {
160 X map[string]any `json:",unknown"`
161 } `json:",inline"`
162 X map[string]RawValue `json:",unknown"`
163 }{},
164 want: structFields{
165 inlinedFallback: &structField{id: 0, index: []int{2}, typ: reflect.TypeOf(map[string]RawValue(nil)), fieldOptions: fieldOptions{name: "X", quotedName: `"X"`, unknown: true}},
166 },
167 }, {
168 name: name("InvalidUTF8"),
169 in: struct {
170 Name string `json:"'\\xde\\xad\\xbe\\xef'"`
171 }{},
172 wantErr: errors.New(`Go struct field Name has JSON object name "ޭ\xbe\xef" with invalid UTF-8`),
173 }, {
174 name: name("DuplicateName"),
175 in: struct {
176 A string `json:"same"`
177 B string `json:"same"`
178 }{},
179 wantErr: errors.New(`Go struct fields A and B conflict over JSON object name "same"`),
180 }, {
181 name: name("BothInlineAndUnknown"),
182 in: struct {
183 A struct{} `json:",inline,unknown"`
184 }{},
185 wantErr: errors.New("Go struct field A cannot have both `inline` and `unknown` specified"),
186 }, {
187 name: name("InlineWithOptions"),
188 in: struct {
189 A struct{} `json:",inline,omitempty"`
190 }{},
191 wantErr: errors.New("Go struct field A cannot have any options other than `inline` or `unknown` specified"),
192 }, {
193 name: name("UnknownWithOptions"),
194 in: struct {
195 A map[string]any `json:",inline,omitempty"`
196 }{},
197 wantErr: errors.New("Go struct field A cannot have any options other than `inline` or `unknown` specified"),
198 }, {
199 name: name("InlineTextMarshaler"),
200 in: struct {
201 A struct{ encoding.TextMarshaler } `json:",inline"`
202 }{},
203 wantErr: errors.New(`inlined Go struct field A of type struct { encoding.TextMarshaler } must not implement JSON marshal or unmarshal methods`),
204 }, {
205 name: name("UnknownJSONMarshalerV1"),
206 in: struct {
207 A struct{ MarshalerV1 } `json:",unknown"`
208 }{},
209 wantErr: errors.New(`inlined Go struct field A of type struct { json.MarshalerV1 } must not implement JSON marshal or unmarshal methods`),
210 }, {
211 name: name("InlineJSONMarshalerV2"),
212 in: struct {
213 A struct{ MarshalerV2 } `json:",inline"`
214 }{},
215 wantErr: errors.New(`inlined Go struct field A of type struct { json.MarshalerV2 } must not implement JSON marshal or unmarshal methods`),
216 }, {
217 name: name("UnknownTextUnmarshaler"),
218 in: struct {
219 A *struct{ encoding.TextUnmarshaler } `json:",unknown"`
220 }{},
221 wantErr: errors.New(`inlined Go struct field A of type struct { encoding.TextUnmarshaler } must not implement JSON marshal or unmarshal methods`),
222 }, {
223 name: name("InlineJSONUnmarshalerV1"),
224 in: struct {
225 A *struct{ UnmarshalerV1 } `json:",inline"`
226 }{},
227 wantErr: errors.New(`inlined Go struct field A of type struct { json.UnmarshalerV1 } must not implement JSON marshal or unmarshal methods`),
228 }, {
229 name: name("UnknownJSONUnmarshalerV2"),
230 in: struct {
231 A struct{ UnmarshalerV2 } `json:",unknown"`
232 }{},
233 wantErr: errors.New(`inlined Go struct field A of type struct { json.UnmarshalerV2 } must not implement JSON marshal or unmarshal methods`),
234 }, {
235 name: name("UnknownStruct"),
236 in: struct {
237 A struct {
238 X, Y, Z string
239 } `json:",unknown"`
240 }{},
241 wantErr: errors.New("inlined Go struct field A of type struct { X string; Y string; Z string } with `unknown` tag must be a Go map of string key or a json.RawValue"),
242 }, {
243 name: name("InlineUnsupported/MapIntKey"),
244 in: struct {
245 A map[int]any `json:",unknown"`
246 }{},
247 wantErr: errors.New(`inlined Go struct field A of type map[int]interface {} must be a Go struct, Go map of string key, or json.RawValue`),
248 }, {
249 name: name("InlineUnsupported/MapNamedStringKey"),
250 in: struct {
251 A map[namedString]any `json:",inline"`
252 }{},
253 wantErr: errors.New(`inlined Go struct field A of type map[json.namedString]interface {} must be a Go struct, Go map of string key, or json.RawValue`),
254 }, {
255 name: name("InlineUnsupported/DoublePointer"),
256 in: struct {
257 A **struct{} `json:",inline"`
258 }{},
259 wantErr: errors.New(`inlined Go struct field A of type *struct {} must be a Go struct, Go map of string key, or json.RawValue`),
260 }, {
261 name: name("DuplicateInline"),
262 in: struct {
263 A map[string]any `json:",inline"`
264 B RawValue `json:",inline"`
265 }{},
266 wantErr: errors.New(`inlined Go struct fields A and B cannot both be a Go map or json.RawValue`),
267 }, {
268 name: name("DuplicateEmbedInline"),
269 in: struct {
270 MapStringAny
271 B RawValue `json:",inline"`
272 }{},
273 wantErr: errors.New(`inlined Go struct fields MapStringAny and B cannot both be a Go map or json.RawValue`),
274 }}
275
276 for _, tt := range tests {
277 t.Run(tt.name.name, func(t *testing.T) {
278 got, err := makeStructFields(reflect.TypeOf(tt.in))
279
280
281 pointers := make(map[*structField]bool)
282 for i := range got.flattened {
283 pointers[&got.flattened[i]] = true
284 }
285 for _, f := range got.byActualName {
286 if !pointers[f] {
287 t.Errorf("%s: byActualName pointer not in flattened", tt.name.where)
288 }
289 }
290 for _, fs := range got.byFoldedName {
291 for _, f := range fs {
292 if !pointers[f] {
293 t.Errorf("%s: byFoldedName pointer not in flattened", tt.name.where)
294 }
295 }
296 }
297
298
299 for i := range got.flattened {
300 got.flattened[i].fncs = nil
301 got.flattened[i].isEmpty = nil
302 }
303 if got.inlinedFallback != nil {
304 got.inlinedFallback.fncs = nil
305 got.inlinedFallback.isEmpty = nil
306 }
307
308
309 if tt.wantErr == nil {
310 tt.want.byActualName = make(map[string]*structField)
311 for i := range tt.want.flattened {
312 f := &tt.want.flattened[i]
313 tt.want.byActualName[f.name] = f
314 }
315 tt.want.byFoldedName = make(map[string][]*structField)
316 for i, f := range tt.want.flattened {
317 foldedName := string(foldName([]byte(f.name)))
318 tt.want.byFoldedName[foldedName] = append(tt.want.byFoldedName[foldedName], &tt.want.flattened[i])
319 }
320 }
321
322
323 var gotErr error
324 if err != nil {
325 gotErr = err.Err
326 }
327
328 if !reflect.DeepEqual(got, tt.want) || !reflect.DeepEqual(gotErr, tt.wantErr) {
329 t.Errorf("%s: makeStructFields(%T):\n\tgot (%v, %v)\n\twant (%v, %v)", tt.name.where, tt.in, got, gotErr, tt.want, tt.wantErr)
330 }
331 })
332 }
333 }
334
335 func TestParseTagOptions(t *testing.T) {
336 tests := []struct {
337 name testName
338 in any
339 wantOpts fieldOptions
340 wantErr error
341 }{{
342 name: name("GoName"),
343 in: struct {
344 FieldName int
345 }{},
346 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`},
347 }, {
348 name: name("GoNameWithOptions"),
349 in: struct {
350 FieldName int `json:",inline"`
351 }{},
352 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true},
353 }, {
354 name: name("Empty"),
355 in: struct {
356 V int `json:""`
357 }{},
358 wantOpts: fieldOptions{name: "V", quotedName: `"V"`},
359 }, {
360 name: name("Unexported"),
361 in: struct {
362 v int `json:"Hello"`
363 }{},
364 wantErr: errors.New("unexported Go struct field v cannot have non-ignored `json:\"Hello\"` tag"),
365 }, {
366 name: name("UnexportedEmpty"),
367 in: struct {
368 v int `json:""`
369 }{},
370 wantErr: errors.New("unexported Go struct field v cannot have non-ignored `json:\"\"` tag"),
371 }, {
372 name: name("EmbedUnexported"),
373 in: struct {
374 unexported
375 }{},
376 wantErr: errors.New("embedded Go struct field unexported of an unexported type must be explicitly ignored with a `json:\"-\"` tag"),
377 }, {
378 name: name("Ignored"),
379 in: struct {
380 V int `json:"-"`
381 }{},
382 wantErr: errIgnoredField,
383 }, {
384 name: name("IgnoredEmbedUnexported"),
385 in: struct {
386 unexported `json:"-"`
387 }{},
388 wantErr: errIgnoredField,
389 }, {
390 name: name("DashComma"),
391 in: struct {
392 V int `json:"-,"`
393 }{},
394 wantErr: errors.New("Go struct field V has malformed `json` tag: invalid trailing ',' character"),
395 }, {
396 name: name("QuotedDashName"),
397 in: struct {
398 V int `json:"'-'"`
399 }{},
400 wantOpts: fieldOptions{hasName: true, name: "-", quotedName: `"-"`},
401 }, {
402 name: name("LatinPunctuationName"),
403 in: struct {
404 V int `json:"$%-/"`
405 }{},
406 wantOpts: fieldOptions{hasName: true, name: "$%-/", quotedName: `"$%-/"`},
407 }, {
408 name: name("QuotedLatinPunctuationName"),
409 in: struct {
410 V int `json:"'$%-/'"`
411 }{},
412 wantOpts: fieldOptions{hasName: true, name: "$%-/", quotedName: `"$%-/"`},
413 }, {
414 name: name("LatinDigitsName"),
415 in: struct {
416 V int `json:"0123456789"`
417 }{},
418 wantOpts: fieldOptions{hasName: true, name: "0123456789", quotedName: `"0123456789"`},
419 }, {
420 name: name("QuotedLatinDigitsName"),
421 in: struct {
422 V int `json:"'0123456789'"`
423 }{},
424 wantOpts: fieldOptions{hasName: true, name: "0123456789", quotedName: `"0123456789"`},
425 }, {
426 name: name("LatinUppercaseName"),
427 in: struct {
428 V int `json:"ABCDEFGHIJKLMOPQRSTUVWXYZ"`
429 }{},
430 wantOpts: fieldOptions{hasName: true, name: "ABCDEFGHIJKLMOPQRSTUVWXYZ", quotedName: `"ABCDEFGHIJKLMOPQRSTUVWXYZ"`},
431 }, {
432 name: name("LatinLowercaseName"),
433 in: struct {
434 V int `json:"abcdefghijklmnopqrstuvwxyz_"`
435 }{},
436 wantOpts: fieldOptions{hasName: true, name: "abcdefghijklmnopqrstuvwxyz_", quotedName: `"abcdefghijklmnopqrstuvwxyz_"`},
437 }, {
438 name: name("GreekName"),
439 in: struct {
440 V string `json:"Ελλάδα"`
441 }{},
442 wantOpts: fieldOptions{hasName: true, name: "Ελλάδα", quotedName: `"Ελλάδα"`},
443 }, {
444 name: name("QuotedGreekName"),
445 in: struct {
446 V string `json:"'Ελλάδα'"`
447 }{},
448 wantOpts: fieldOptions{hasName: true, name: "Ελλάδα", quotedName: `"Ελλάδα"`},
449 }, {
450 name: name("ChineseName"),
451 in: struct {
452 V string `json:"世界"`
453 }{},
454 wantOpts: fieldOptions{hasName: true, name: "世界", quotedName: `"世界"`},
455 }, {
456 name: name("QuotedChineseName"),
457 in: struct {
458 V string `json:"'世界'"`
459 }{},
460 wantOpts: fieldOptions{hasName: true, name: "世界", quotedName: `"世界"`},
461 }, {
462 name: name("PercentSlashName"),
463 in: struct {
464 V int `json:"text/html%"`
465 }{},
466 wantOpts: fieldOptions{hasName: true, name: "text/html%", quotedName: `"text/html%"`},
467 }, {
468 name: name("QuotedPercentSlashName"),
469 in: struct {
470 V int `json:"'text/html%'"`
471 }{},
472 wantOpts: fieldOptions{hasName: true, name: "text/html%", quotedName: `"text/html%"`},
473 }, {
474 name: name("PunctuationName"),
475 in: struct {
476 V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "`
477 }{},
478 wantOpts: fieldOptions{hasName: true, name: "!#$%&()*+-./:;<=>?@[]^_{|}~ ", quotedName: `"!#$%&()*+-./:;<=>?@[]^_{|}~ "`},
479 }, {
480 name: name("QuotedPunctuationName"),
481 in: struct {
482 V string `json:"'!#$%&()*+-./:;<=>?@[]^_{|}~ '"`
483 }{},
484 wantOpts: fieldOptions{hasName: true, name: "!#$%&()*+-./:;<=>?@[]^_{|}~ ", quotedName: `"!#$%&()*+-./:;<=>?@[]^_{|}~ "`},
485 }, {
486 name: name("EmptyName"),
487 in: struct {
488 V int `json:"''"`
489 }{},
490 wantOpts: fieldOptions{hasName: true, name: "", quotedName: `""`},
491 }, {
492 name: name("SpaceName"),
493 in: struct {
494 V int `json:"' '"`
495 }{},
496 wantOpts: fieldOptions{hasName: true, name: " ", quotedName: `" "`},
497 }, {
498 name: name("CommaQuotes"),
499 in: struct {
500 V int `json:"',\\'\"\\\"'"`
501 }{},
502 wantOpts: fieldOptions{hasName: true, name: `,'""`, quotedName: `",'\"\""`},
503 }, {
504 name: name("SingleComma"),
505 in: struct {
506 V int `json:","`
507 }{},
508 wantErr: errors.New("Go struct field V has malformed `json` tag: invalid trailing ',' character"),
509 }, {
510 name: name("SuperfluousCommas"),
511 in: struct {
512 V int `json:",,,,\"\",,inline,unknown,,,,"`
513 }{},
514 wantErr: errors.New("Go struct field V has malformed `json` tag: invalid character ',' at start of option (expecting Unicode letter or single quote)"),
515 }, {
516 name: name("NoCaseOption"),
517 in: struct {
518 FieldName int `json:",nocase"`
519 }{},
520 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, nocase: true},
521 }, {
522 name: name("InlineOption"),
523 in: struct {
524 FieldName int `json:",inline"`
525 }{},
526 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true},
527 }, {
528 name: name("UnknownOption"),
529 in: struct {
530 FieldName int `json:",unknown"`
531 }{},
532 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, unknown: true},
533 }, {
534 name: name("OmitZeroOption"),
535 in: struct {
536 FieldName int `json:",omitzero"`
537 }{},
538 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, omitzero: true},
539 }, {
540 name: name("OmitEmptyOption"),
541 in: struct {
542 FieldName int `json:",omitempty"`
543 }{},
544 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, omitempty: true},
545 }, {
546 name: name("StringOption"),
547 in: struct {
548 FieldName int `json:",string"`
549 }{},
550 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, string: true},
551 }, {
552 name: name("FormatOptionEqual"),
553 in: struct {
554 FieldName int `json:",format=fizzbuzz"`
555 }{},
556 wantErr: errors.New("Go struct field FieldName is missing value for `format` tag option"),
557 }, {
558 name: name("FormatOptionColon"),
559 in: struct {
560 FieldName int `json:",format:fizzbuzz"`
561 }{},
562 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "fizzbuzz"},
563 }, {
564 name: name("FormatOptionQuoted"),
565 in: struct {
566 FieldName int `json:",format:'2006-01-02'"`
567 }{},
568 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "2006-01-02"},
569 }, {
570 name: name("FormatOptionInvalid"),
571 in: struct {
572 FieldName int `json:",format:'2006-01-02"`
573 }{},
574 wantErr: errors.New("Go struct field FieldName has malformed value for `format` tag option: single-quoted string not terminated: '2006-01-0..."),
575 }, {
576 name: name("FormatOptionNotLast"),
577 in: struct {
578 FieldName int `json:",format:alpha,ordered"`
579 }{},
580 wantErr: errors.New("Go struct field FieldName has `format` tag option that was not specified last"),
581 }, {
582 name: name("AllOptions"),
583 in: struct {
584 FieldName int `json:",nocase,inline,unknown,omitzero,omitempty,string,format:format"`
585 }{},
586 wantOpts: fieldOptions{
587 name: "FieldName",
588 quotedName: `"FieldName"`,
589 nocase: true,
590 inline: true,
591 unknown: true,
592 omitzero: true,
593 omitempty: true,
594 string: true,
595 format: "format",
596 },
597 }, {
598 name: name("AllOptionsQuoted"),
599 in: struct {
600 FieldName int `json:",'nocase','inline','unknown','omitzero','omitempty','string','format':'format'"`
601 }{},
602 wantErr: errors.New("Go struct field FieldName has unnecessarily quoted appearance of `'nocase'` tag option; specify `nocase` instead"),
603 }, {
604 name: name("AllOptionsCaseSensitive"),
605 in: struct {
606 FieldName int `json:",NOCASE,INLINE,UNKNOWN,OMITZERO,OMITEMPTY,STRING,FORMAT:FORMAT"`
607 }{},
608 wantErr: errors.New("Go struct field FieldName has invalid appearance of `NOCASE` tag option; specify `nocase` instead"),
609 }, {
610 name: name("AllOptionsSpaceSensitive"),
611 in: struct {
612 FieldName int `json:", nocase , inline , unknown , omitzero , omitempty , string , format:format "`
613 }{},
614 wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid character ' ' at start of option (expecting Unicode letter or single quote)"),
615 }, {
616 name: name("UnknownTagOption"),
617 in: struct {
618 FieldName int `json:",inline,whoknows,string"`
619 }{},
620 wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true, string: true},
621 }, {
622 name: name("MalformedQuotedString/MissingQuote"),
623 in: struct {
624 FieldName int `json:"'hello,string"`
625 }{},
626 wantErr: errors.New("Go struct field FieldName has malformed `json` tag: single-quoted string not terminated: 'hello,str..."),
627 }, {
628 name: name("MalformedQuotedString/MissingComma"),
629 in: struct {
630 FieldName int `json:"'hello'inline,string"`
631 }{},
632 wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid character 'i' before next option (expecting ',')"),
633 }, {
634 name: name("MalformedQuotedString/InvalidEscape"),
635 in: struct {
636 FieldName int `json:"'hello\\u####',inline,string"`
637 }{},
638 wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid single-quoted string: 'hello\\u####'"),
639 }, {
640 name: name("MisnamedTag"),
641 in: struct {
642 V int `jsom:"Misnamed"`
643 }{},
644 wantOpts: fieldOptions{name: "V", quotedName: `"V"`},
645 }}
646
647 for _, tt := range tests {
648 t.Run(tt.name.name, func(t *testing.T) {
649 fs := reflect.TypeOf(tt.in).Field(0)
650 gotOpts, gotErr := parseFieldOptions(fs)
651 if !reflect.DeepEqual(gotOpts, tt.wantOpts) || !reflect.DeepEqual(gotErr, tt.wantErr) {
652 t.Errorf("%s: parseFieldOptions(%T) = (%v, %v), want (%v, %v)", tt.name.where, tt.in, gotOpts, gotErr, tt.wantOpts, tt.wantErr)
653 }
654 })
655 }
656 }
657
View as plain text