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 }
463 for _, tt := range tests {
464 t.Run(tt.Name, func(t *testing.T) {
465 dec := NewDecoder(strings.NewReader(tt.json))
466 for i, want := range tt.expTokens {
467 var got any
468 var err error
469
470 if dt, ok := want.(decodeThis); ok {
471 want = dt.v
472 err = dec.Decode(&got)
473 } else {
474 got, err = dec.Token()
475 }
476 if errWant, ok := want.(error); ok {
477 if err == nil || !reflect.DeepEqual(err, errWant) {
478 t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: %v", tt.Where, tt.json, err, errWant)
479 }
480 break
481 } else if err != nil {
482 t.Fatalf("%s:\n\tinput: %s\n\tgot error: %v\n\twant error: nil", tt.Where, tt.json, err)
483 }
484 if !reflect.DeepEqual(got, want) {
485 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)
486 }
487 }
488 })
489 }
490 }
491
492
493 func TestHTTPDecoding(t *testing.T) {
494 const raw = `{ "foo": "bar" }`
495
496 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
497 w.Write([]byte(raw))
498 }))
499 defer ts.Close()
500 res, err := http.Get(ts.URL)
501 if err != nil {
502 log.Fatalf("http.Get error: %v", err)
503 }
504 defer res.Body.Close()
505
506 foo := struct {
507 Foo string
508 }{}
509
510 d := NewDecoder(res.Body)
511 err = d.Decode(&foo)
512 if err != nil {
513 t.Fatalf("Decode error: %v", err)
514 }
515 if foo.Foo != "bar" {
516 t.Errorf(`Decode: got %q, want "bar"`, foo.Foo)
517 }
518
519
520 err = d.Decode(&foo)
521 if err != io.EOF {
522 t.Errorf("Decode error:\n\tgot: %v\n\twant: io.EOF", err)
523 }
524 }
525
View as plain text