1
2
3
4
5
6
7 package json
8
9 import (
10 "bytes"
11 "fmt"
12 "io"
13 "log"
14 "net"
15 "net/http"
16 "net/http/httptest"
17 "path"
18 "reflect"
19 "runtime"
20 "runtime/debug"
21 "strings"
22 "testing"
23 )
24
25
26
27
28 type CaseName struct {
29 Name string
30 Where CasePos
31 }
32
33
34 func Name(s string) (c CaseName) {
35 c.Name = s
36 runtime.Callers(2, c.Where.pc[:])
37 return c
38 }
39
40
41 type CasePos struct{ pc [1]uintptr }
42
43 func (pos CasePos) String() string {
44 frames := runtime.CallersFrames(pos.pc[:])
45 frame, _ := frames.Next()
46 return fmt.Sprintf("%s:%d", path.Base(frame.File), frame.Line)
47 }
48
49
50
51 var streamTest = []any{
52 0.1,
53 "hello",
54 nil,
55 true,
56 false,
57 []any{"a", "b", "c"},
58 map[string]any{"K": "Kelvin", "ß": "long s"},
59 3.14,
60 }
61
62 var streamEncoded = `0.1
63 "hello"
64 null
65 true
66 false
67 ["a","b","c"]
68 {"ß":"long s","K":"Kelvin"}
69 3.14
70 `
71
72 func TestEncoder(t *testing.T) {
73 for i := 0; i <= len(streamTest); i++ {
74 var buf strings.Builder
75 enc := NewEncoder(&buf)
76
77 enc.SetIndent(">", ".")
78 enc.SetIndent("", "")
79 for j, v := range streamTest[0:i] {
80 if err := enc.Encode(v); err != nil {
81 t.Fatalf("#%d.%d Encode error: %v", i, j, err)
82 }
83 }
84 if got, want := buf.String(), nlines(streamEncoded, i); got != want {
85 t.Errorf("encoding %d items: mismatch:", i)
86 diff(t, []byte(got), []byte(want))
87 break
88 }
89 }
90 }
91
92 func TestEncoderErrorAndReuseEncodeState(t *testing.T) {
93
94 percent := debug.SetGCPercent(-1)
95 defer debug.SetGCPercent(percent)
96
97
98 type Dummy struct {
99 Name string
100 Next *Dummy
101 }
102 dummy := Dummy{Name: "Dummy"}
103 dummy.Next = &dummy
104
105 var buf bytes.Buffer
106 enc := NewEncoder(&buf)
107 if err := enc.Encode(dummy); err == nil {
108 t.Errorf("Encode(dummy) error: got nil, want non-nil")
109 }
110
111 type Data struct {
112 A string
113 I int
114 }
115 want := Data{A: "a", I: 1}
116 if err := enc.Encode(want); err != nil {
117 t.Errorf("Marshal error: %v", err)
118 }
119
120 var got Data
121 if err := Unmarshal(buf.Bytes(), &got); err != nil {
122 t.Errorf("Unmarshal error: %v", err)
123 }
124 if got != want {
125 t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %v\n\twant: %v", got, want)
126 }
127 }
128
129 var streamEncodedIndent = `0.1
130 "hello"
131 null
132 true
133 false
134 [
135 >."a",
136 >."b",
137 >."c"
138 >]
139 {
140 >."ß": "long s",
141 >."K": "Kelvin"
142 >}
143 3.14
144 `
145
146 func TestEncoderIndent(t *testing.T) {
147 var buf strings.Builder
148 enc := NewEncoder(&buf)
149 enc.SetIndent(">", ".")
150 for _, v := range streamTest {
151 enc.Encode(v)
152 }
153 if got, want := buf.String(), streamEncodedIndent; got != want {
154 t.Errorf("Encode mismatch:\ngot:\n%s\n\nwant:\n%s", got, want)
155 diff(t, []byte(got), []byte(want))
156 }
157 }
158
159 type strMarshaler string
160
161 func (s strMarshaler) MarshalJSON() ([]byte, error) {
162 return []byte(s), nil
163 }
164
165 type strPtrMarshaler string
166
167 func (s *strPtrMarshaler) MarshalJSON() ([]byte, error) {
168 return []byte(*s), nil
169 }
170
171 func TestEncoderSetEscapeHTML(t *testing.T) {
172 var c C
173 var ct CText
174 var tagStruct struct {
175 Valid int `json:"<>&#! "`
176 Invalid int `json:"\\"`
177 }
178
179
180
181
182 marshalerStruct := &struct {
183 NonPtr strMarshaler
184 Ptr strPtrMarshaler
185 }{`"<str>"`, `"<str>"`}
186
187
188 stringOption := struct {
189 Bar string `json:"bar,string"`
190 }{`<html>foobar</html>`}
191
192 tests := []struct {
193 CaseName
194 v any
195 wantEscape string
196 want string
197 }{
198 {Name("c"), c, `"\u003c\u0026\u003e"`, `"<&>"`},
199 {Name("ct"), ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`},
200 {Name(`"<&>"`), "<&>", `"\u003c\u0026\u003e"`, `"<&>"`},
201 {
202 Name("tagStruct"), tagStruct,
203 `{"\u003c\u003e\u0026#! ":0,"Invalid":0}`,
204 `{"<>&#! ":0,"Invalid":0}`,
205 },
206 {
207 Name(`"<str>"`), marshalerStruct,
208 `{"NonPtr":"\u003cstr\u003e","Ptr":"\u003cstr\u003e"}`,
209 `{"NonPtr":"<str>","Ptr":"<str>"}`,
210 },
211 {
212 Name("stringOption"), stringOption,
213 `{"bar":"\"\\u003chtml\\u003efoobar\\u003c/html\\u003e\""}`,
214 `{"bar":"\"<html>foobar</html>\""}`,
215 },
216 }
217 for _, tt := range tests {
218 t.Run(tt.Name, func(t *testing.T) {
219 var buf strings.Builder
220 enc := NewEncoder(&buf)
221 if err := enc.Encode(tt.v); err != nil {
222 t.Fatalf("%s: Encode(%s) error: %s", tt.Where, tt.Name, err)
223 }
224 if got := strings.TrimSpace(buf.String()); got != tt.wantEscape {
225 t.Errorf("%s: Encode(%s):\n\tgot: %s\n\twant: %s", tt.Where, tt.Name, got, tt.wantEscape)
226 }
227 buf.Reset()
228 enc.SetEscapeHTML(false)
229 if err := enc.Encode(tt.v); err != nil {
230 t.Fatalf("%s: SetEscapeHTML(false) Encode(%s) error: %s", tt.Where, tt.Name, err)
231 }
232 if got := strings.TrimSpace(buf.String()); got != tt.want {
233 t.Errorf("%s: SetEscapeHTML(false) Encode(%s):\n\tgot: %s\n\twant: %s",
234 tt.Where, tt.Name, got, tt.want)
235 }
236 })
237 }
238 }
239
240 func TestDecoder(t *testing.T) {
241 for i := 0; i <= len(streamTest); i++ {
242
243
244
245
246
247 var buf bytes.Buffer
248 for _, c := range nlines(streamEncoded, i) {
249 if c != '\n' {
250 buf.WriteRune(c)
251 }
252 }
253 out := make([]any, i)
254 dec := NewDecoder(&buf)
255 for j := range out {
256 if err := dec.Decode(&out[j]); err != nil {
257 t.Fatalf("decode #%d/%d error: %v", j, i, err)
258 }
259 }
260 if !reflect.DeepEqual(out, streamTest[0:i]) {
261 t.Errorf("decoding %d items: mismatch:", i)
262 for j := range out {
263 if !reflect.DeepEqual(out[j], streamTest[j]) {
264 t.Errorf("#%d:\n\tgot: %v\n\twant: %v", j, out[j], streamTest[j])
265 }
266 }
267 break
268 }
269 }
270 }
271
272 func TestDecoderBuffered(t *testing.T) {
273 r := strings.NewReader(`{"Name": "Gopher"} extra `)
274 var m struct {
275 Name string
276 }
277 d := NewDecoder(r)
278 err := d.Decode(&m)
279 if err != nil {
280 t.Fatal(err)
281 }
282 if m.Name != "Gopher" {
283 t.Errorf("Name = %s, want Gopher", m.Name)
284 }
285 rest, err := io.ReadAll(d.Buffered())
286 if err != nil {
287 t.Fatal(err)
288 }
289 if got, want := string(rest), " extra "; got != want {
290 t.Errorf("Remaining = %s, want %s", got, want)
291 }
292 }
293
294 func nlines(s string, n int) string {
295 if n <= 0 {
296 return ""
297 }
298 for i, c := range s {
299 if c == '\n' {
300 if n--; n == 0 {
301 return s[0 : i+1]
302 }
303 }
304 }
305 return s
306 }
307
308 func TestRawMessage(t *testing.T) {
309 var data struct {
310 X float64
311 Id RawMessage
312 Y float32
313 }
314 const raw = `["\u0056",null]`
315 const want = `{"X":0.1,"Id":["\u0056",null],"Y":0.2}`
316 err := Unmarshal([]byte(want), &data)
317 if err != nil {
318 t.Fatalf("Unmarshal error: %v", err)
319 }
320 if string([]byte(data.Id)) != raw {
321 t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", []byte(data.Id), raw)
322 }
323 got, err := Marshal(&data)
324 if err != nil {
325 t.Fatalf("Marshal error: %v", err)
326 }
327 if string(got) != want {
328 t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
329 }
330 }
331
332 func TestNullRawMessage(t *testing.T) {
333 var data struct {
334 X float64
335 Id RawMessage
336 IdPtr *RawMessage
337 Y float32
338 }
339 const want = `{"X":0.1,"Id":null,"IdPtr":null,"Y":0.2}`
340 err := Unmarshal([]byte(want), &data)
341 if err != nil {
342 t.Fatalf("Unmarshal error: %v", err)
343 }
344 if want, got := "null", string(data.Id); want != got {
345 t.Fatalf("Unmarshal:\n\tgot: %s\n\twant: %s", got, want)
346 }
347 if data.IdPtr != nil {
348 t.Fatalf("pointer mismatch: got non-nil, want nil")
349 }
350 got, err := Marshal(&data)
351 if err != nil {
352 t.Fatalf("Marshal error: %v", err)
353 }
354 if string(got) != want {
355 t.Fatalf("Marshal:\n\tgot: %s\n\twant: %s", got, want)
356 }
357 }
358
359 func TestBlocking(t *testing.T) {
360 tests := []struct {
361 CaseName
362 in string
363 }{
364 {Name(""), `{"x": 1}`},
365 {Name(""), `[1, 2, 3]`},
366 }
367 for _, tt := range tests {
368 t.Run(tt.Name, func(t *testing.T) {
369 r, w := net.Pipe()
370 go w.Write([]byte(tt.in))
371 var val any
372
373
374
375 if err := NewDecoder(r).Decode(&val); err != nil {
376 t.Errorf("%s: NewDecoder(%s).Decode error: %v", tt.Where, tt.in, err)
377 }
378 r.Close()
379 w.Close()
380 })
381 }
382 }
383
384 type decodeThis struct {
385 v any
386 }
387
388 func TestDecodeInStream(t *testing.T) {
389 tests := []struct {
390 CaseName
391 json string
392 expTokens []any
393 }{
394
395 {CaseName: Name(""), json: `10`, expTokens: []any{float64(10)}},
396 {CaseName: Name(""), json: ` [10] `, expTokens: []any{
397 Delim('['), float64(10), Delim(']')}},
398 {CaseName: Name(""), json: ` [false,10,"b"] `, expTokens: []any{
399 Delim('['), false, float64(10), "b", Delim(']')}},
400 {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
401 Delim('{'), "a", float64(1), Delim('}')}},
402 {CaseName: Name(""), json: `{"a": 1, "b":"3"}`, expTokens: []any{
403 Delim('{'), "a", float64(1), "b", "3", Delim('}')}},
404 {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
405 Delim('['),
406 Delim('{'), "a", float64(1), Delim('}'),
407 Delim('{'), "a", float64(2), Delim('}'),
408 Delim(']')}},
409 {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
410 Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'),
411 Delim('}')}},
412 {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
413 Delim('{'), "obj", Delim('['),
414 Delim('{'), "a", float64(1), Delim('}'),
415 Delim(']'), Delim('}')}},
416
417
418 {CaseName: Name(""), json: `{ "a": 1 }`, expTokens: []any{
419 Delim('{'), "a",
420 decodeThis{float64(1)},
421 Delim('}')}},
422 {CaseName: Name(""), json: ` [ { "a" : 1 } ] `, expTokens: []any{
423 Delim('['),
424 decodeThis{map[string]any{"a": float64(1)}},
425 Delim(']')}},
426 {CaseName: Name(""), json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{
427 Delim('['),
428 decodeThis{map[string]any{"a": float64(1)}},
429 decodeThis{map[string]any{"a": float64(2)}},
430 Delim(']')}},
431 {CaseName: Name(""), json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{
432 Delim('{'), "obj", Delim('['),
433 decodeThis{map[string]any{"a": float64(1)}},
434 Delim(']'), Delim('}')}},
435
436 {CaseName: Name(""), json: `{"obj": {"a": 1}}`, expTokens: []any{
437 Delim('{'), "obj",
438 decodeThis{map[string]any{"a": float64(1)}},
439 Delim('}')}},
440 {CaseName: Name(""), json: `{"obj": [{"a": 1}]}`, expTokens: []any{
441 Delim('{'), "obj",
442 decodeThis{[]any{
443 map[string]any{"a": float64(1)},
444 }},
445 Delim('}')}},
446 {CaseName: Name(""), json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{
447 Delim('['),
448 decodeThis{map[string]any{"a": float64(1)}},
449 decodeThis{&SyntaxError{"expected comma after array element", 11}},
450 }},
451 {CaseName: Name(""), json: `{ "` + strings.Repeat("a", 513) + `" 1 }`, expTokens: []any{
452 Delim('{'), strings.Repeat("a", 513),
453 decodeThis{&SyntaxError{"expected colon after object key", 518}},
454 }},
455 {CaseName: Name(""), json: `{ "\a" }`, expTokens: []any{
456 Delim('{'),
457 &SyntaxError{"invalid character 'a' in string escape code", 3},
458 }},
459 {CaseName: Name(""), json: ` \a`, expTokens: []any{
460 &SyntaxError{"invalid character '\\\\' looking for beginning of value", 1},
461 }},
462 {CaseName: Name(""), json: `,`, expTokens: []any{
463 &SyntaxError{"invalid character ',' looking for beginning of value", 0},
464 }},
465 }
466 for _, tt := range tests {
467 t.Run(tt.Name, func(t *testing.T) {
468 dec := NewDecoder(strings.NewReader(tt.json))
469 for i, want := range tt.expTokens {
470 var got any
471 var err error
472
473 wantMore := true
474 switch want {
475 case Delim(']'), Delim('}'):
476 wantMore = false
477 }
478 if got := dec.More(); got != wantMore {
479 t.Fatalf("%s:\n\tinput: %s\n\tdec.More() = %v, want %v (next token: %T(%v))", tt.Where, tt.json, got, wantMore, want, want)
480 }
481
482 if dt, ok := want.(decodeThis); ok {
483 want = dt.v
484 err = dec.Decode(&got)
485 } else {
486 got, err = dec.Token()
487 }
488 if errWant, ok := want.(error); ok {
489 if err == nil || !reflect.DeepEqual(err, errWant) {
490 t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: %v", tt.Where, tt.json, err, errWant)
491 }
492 break
493 } else if err != nil {
494 t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: nil", tt.Where, tt.json, err)
495 }
496 if !reflect.DeepEqual(got, want) {
497 t.Fatalf("%s: token %d:\n\tinput: %s\n\tgot: %T(%v)\n\twant: %T(%v)", tt.Where, i, tt.json, got, got, want, want)
498 }
499 }
500 })
501 }
502 }
503
504
505 func TestHTTPDecoding(t *testing.T) {
506 const raw = `{ "foo": "bar" }`
507
508 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
509 w.Write([]byte(raw))
510 }))
511 defer ts.Close()
512 res, err := http.Get(ts.URL)
513 if err != nil {
514 log.Fatalf("http.Get error: %v", err)
515 }
516 defer res.Body.Close()
517
518 foo := struct {
519 Foo string
520 }{}
521
522 d := NewDecoder(res.Body)
523 err = d.Decode(&foo)
524 if err != nil {
525 t.Fatalf("Decode error: %v", err)
526 }
527 if foo.Foo != "bar" {
528 t.Errorf(`Decode: got %q, want "bar"`, foo.Foo)
529 }
530
531
532 err = d.Decode(&foo)
533 if err != io.EOF {
534 t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err)
535 }
536 }
537
538 func TestTokenTruncation(t *testing.T) {
539 tests := []struct {
540 in string
541 err error
542 }{
543 {in: ``, err: io.EOF},
544 {in: `{`, err: io.EOF},
545 {in: `{"`, err: io.ErrUnexpectedEOF},
546 {in: `{"k"`, err: io.EOF},
547 {in: `{"k":`, err: io.EOF},
548 {in: `{"k",`, err: &SyntaxError{"invalid character ',' after object key", int64(len(`{"k"`))}},
549 {in: `{"k"}`, err: &SyntaxError{"invalid character '}' after object key", int64(len(`{"k"`))}},
550 {in: ` [0`, err: io.EOF},
551 {in: `[0.`, err: io.ErrUnexpectedEOF},
552 {in: `[0. `, err: &SyntaxError{"invalid character ' ' after decimal point in numeric literal", int64(len(`[0.`))}},
553 {in: `[0,`, err: io.EOF},
554 {in: `[0:`, err: &SyntaxError{"invalid character ':' after array element", int64(len(`[0`))}},
555 {in: `n`, err: io.ErrUnexpectedEOF},
556 {in: `nul`, err: io.ErrUnexpectedEOF},
557 {in: `fal `, err: &SyntaxError{"invalid character ' ' in literal false (expecting 's')", int64(len(`fal `))}},
558 {in: `false`, err: io.EOF},
559 }
560 for _, tt := range tests {
561 d := NewDecoder(strings.NewReader(tt.in))
562 for i := 0; true; i++ {
563 if _, err := d.Token(); err != nil {
564 if !reflect.DeepEqual(err, tt.err) {
565 t.Errorf("`%s`: %d.Token error = %#v, want %v", tt.in, i, err, tt.err)
566 }
567 break
568 }
569 }
570 }
571 }
572
573 func TestDecoderInputOffset(t *testing.T) {
574 const input = ` [
575 [ ] , [ "one" ] , [ "one" , "two" ] ,
576 { } , { "alpha" : "bravo" } , { "alpha" : "bravo" , "fizz" : "buzz" }
577 ] `
578 wantOffsets := []int64{
579 0, 1, 2, 5, 6, 7, 8, 9, 12, 13, 18, 19, 20, 21, 24, 25, 30, 31,
580 38, 39, 40, 41, 46, 47, 48, 49, 52, 53, 60, 61, 70, 71, 72, 73,
581 76, 77, 84, 85, 94, 95, 103, 104, 112, 113, 114, 116, 117, 117,
582 117, 117,
583 }
584 wantMores := []bool{
585 true, true, false, true, true, false, true, true, true, false,
586 true, false, true, true, true, false, true, true, true, true,
587 true, false, false, false, false,
588 }
589
590 d := NewDecoder(strings.NewReader(input))
591 checkOffset := func() {
592 t.Helper()
593 got := d.InputOffset()
594 if len(wantOffsets) == 0 {
595 t.Fatalf("InputOffset = %d, want nil", got)
596 }
597 want := wantOffsets[0]
598 if got != want {
599 t.Fatalf("InputOffset = %d, want %d", got, want)
600 }
601 wantOffsets = wantOffsets[1:]
602 }
603 checkMore := func() {
604 t.Helper()
605 got := d.More()
606 if len(wantMores) == 0 {
607 t.Fatalf("More = %v, want nil", got)
608 }
609 want := wantMores[0]
610 if got != want {
611 t.Fatalf("More = %v, want %v", got, want)
612 }
613 wantMores = wantMores[1:]
614 }
615 checkOffset()
616 checkMore()
617 checkOffset()
618 for {
619 if _, err := d.Token(); err == io.EOF {
620 break
621 } else if err != nil {
622 t.Fatalf("Token error: %v", err)
623 }
624 checkOffset()
625 checkMore()
626 checkOffset()
627 }
628 checkOffset()
629 checkMore()
630 checkOffset()
631
632 if len(wantOffsets)+len(wantMores) > 0 {
633 t.Fatal("unconsumed testdata")
634 }
635 }
636
View as plain text