blob: 3e96fa67ae7ca08b1338f4db84eded6b2dd152c4 [file] [log] [blame]
Joe Tsai879b18d2018-08-03 17:22:24 -07001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package json
6
7import (
8 "math"
9 "strings"
10 "testing"
11 "unicode/utf8"
12
13 "github.com/google/go-cmp/cmp"
14 "github.com/google/go-cmp/cmp/cmpopts"
15)
16
17func Test(t *testing.T) {
18 const space = " \n\r\t"
19 var V = ValueOf
20 type Arr = []Value
21 type Obj = [][2]Value
22
23 tests := []struct {
24 in string
25 wantVal Value
26 wantOut string
27 wantOutIndent string
28 wantErr string
29 }{{
30 in: ``,
31 wantErr: `unexpected EOF`,
32 }, {
33 in: space,
34 wantErr: `unexpected EOF`,
35 }, {
36 in: space + `null` + space,
37 wantVal: V(nil),
38 wantOut: `null`,
39 wantOutIndent: `null`,
40 }, {
41 in: space + `true` + space,
42 wantVal: V(true),
43 wantOut: `true`,
44 wantOutIndent: `true`,
45 }, {
46 in: space + `false` + space,
47 wantVal: V(false),
48 wantOut: `false`,
49 wantOutIndent: `false`,
50 }, {
51 in: space + `0` + space,
52 wantVal: V(0.0),
53 wantOut: `0`,
54 wantOutIndent: `0`,
55 }, {
56 in: space + `"hello"` + space,
57 wantVal: V("hello"),
58 wantOut: `"hello"`,
59 wantOutIndent: `"hello"`,
60 }, {
61 in: space + `[]` + space,
62 wantVal: V(Arr{}),
63 wantOut: `[]`,
64 wantOutIndent: `[]`,
65 }, {
66 in: space + `{}` + space,
67 wantVal: V(Obj{}),
68 wantOut: `{}`,
69 wantOutIndent: `{}`,
70 }, {
71 in: `null#invalid`,
72 wantErr: `8 bytes of unconsumed input`,
73 }, {
74 in: `0#invalid`,
75 wantErr: `8 bytes of unconsumed input`,
76 }, {
77 in: `"hello"#invalid`,
78 wantErr: `8 bytes of unconsumed input`,
79 }, {
80 in: `[]#invalid`,
81 wantErr: `8 bytes of unconsumed input`,
82 }, {
83 in: `{}#invalid`,
84 wantErr: `8 bytes of unconsumed input`,
85 }, {
86 in: `[truee,true]`,
87 wantErr: `invalid "truee" as literal`,
88 }, {
89 in: `[falsee,false]`,
90 wantErr: `invalid "falsee" as literal`,
91 }, {
92 in: `[`,
93 wantErr: `unexpected EOF`,
94 }, {
95 in: `[{}]`,
96 wantVal: V(Arr{V(Obj{})}),
97 wantOut: "[{}]",
98 wantOutIndent: "[\n\t{}\n]",
99 }, {
100 in: `[{]}`,
101 wantErr: `invalid character ']' at start of string`,
102 }, {
103 in: `[,]`,
104 wantErr: `invalid "," as value`,
105 }, {
106 in: `{,}`,
107 wantErr: `invalid character ',' at start of string`,
108 }, {
109 in: `{"key""val"}`,
110 wantErr: `invalid character '"', expected ':' in object`,
111 }, {
112 in: `["elem0""elem1"]`,
113 wantErr: `invalid character '"', expected ']' at end of array`,
114 }, {
115 in: `{"hello"`,
116 wantErr: `unexpected EOF`,
117 }, {
118 in: `{"hello"}`,
119 wantErr: `invalid character '}', expected ':' in object`,
120 }, {
121 in: `{"hello":`,
122 wantErr: `unexpected EOF`,
123 }, {
124 in: `{"hello":}`,
125 wantErr: `invalid "}" as value`,
126 }, {
127 in: `{"hello":"goodbye"`,
128 wantErr: `unexpected EOF`,
129 }, {
130 in: `{"hello":"goodbye"]`,
131 wantErr: `invalid character ']', expected '}' at end of object`,
132 }, {
133 in: `{"hello":"goodbye"}`,
134 wantVal: V(Obj{{V("hello"), V("goodbye")}}),
135 wantOut: `{"hello":"goodbye"}`,
136 wantOutIndent: "{\n\t\"hello\": \"goodbye\"\n}",
137 }, {
138 in: `{"hello":"goodbye",}`,
139 wantErr: `invalid character '}' at start of string`,
140 }, {
141 in: `{"k":"v1","k":"v2"}`,
142 wantVal: V(Obj{
143 {V("k"), V("v1")}, {V("k"), V("v2")},
144 }),
145 wantOut: `{"k":"v1","k":"v2"}`,
146 wantOutIndent: "{\n\t\"k\": \"v1\",\n\t\"k\": \"v2\"\n}",
147 }, {
148 in: `{"k":{"k":{"k":"v"}}}`,
149 wantVal: V(Obj{
150 {V("k"), V(Obj{
151 {V("k"), V(Obj{
152 {V("k"), V("v")},
153 })},
154 })},
155 }),
156 wantOut: `{"k":{"k":{"k":"v"}}}`,
157 wantOutIndent: "{\n\t\"k\": {\n\t\t\"k\": {\n\t\t\t\"k\": \"v\"\n\t\t}\n\t}\n}",
158 }, {
159 in: `{"k":{"k":{"k":"v1","k":"v2"}}}`,
160 wantVal: V(Obj{
161 {V("k"), V(Obj{
162 {V("k"), V(Obj{
163 {V("k"), V("v1")},
164 {V("k"), V("v2")},
165 })},
166 })},
167 }),
168 wantOut: `{"k":{"k":{"k":"v1","k":"v2"}}}`,
169 wantOutIndent: "{\n\t\"k\": {\n\t\t\"k\": {\n\t\t\t\"k\": \"v1\",\n\t\t\t\"k\": \"v2\"\n\t\t}\n\t}\n}",
170 }, {
171 in: " x",
172 wantErr: `syntax error (line 1:3)`,
173 }, {
174 in: `["💩"x`,
175 wantErr: `syntax error (line 1:5)`,
176 }, {
177 in: "\n\n[\"🔥🔥🔥\"x",
178 wantErr: `syntax error (line 3:7)`,
179 }, {
180 in: `["👍🏻👍🏿"x`,
181 wantErr: `syntax error (line 1:8)`, // multi-rune emojis; could be column:6
182 }, {
183 in: "\"\x00\"",
184 wantErr: `invalid character '\x00' in string`,
185 }, {
186 in: "\"\xff\"",
187 wantErr: `invalid UTF-8 detected`,
188 wantVal: V(string("\xff")),
189 }, {
190 in: `"` + string(utf8.RuneError) + `"`,
191 wantVal: V(string(utf8.RuneError)),
192 wantOut: `"` + string(utf8.RuneError) + `"`,
193 }, {
194 in: `"\uFFFD"`,
195 wantVal: V(string(utf8.RuneError)),
196 wantOut: `"` + string(utf8.RuneError) + `"`,
197 }, {
198 in: `"\x"`,
199 wantErr: `invalid escape code "\\x" in string`,
200 }, {
201 in: `"\uXXXX"`,
202 wantErr: `invalid escape code "\\uXXXX" in string`,
203 }, {
204 in: `"\uDEAD"`, // unmatched surrogate pair
205 wantErr: `unexpected EOF`,
206 }, {
207 in: `"\uDEAD\uBEEF"`, // invalid surrogate half
208 wantErr: `invalid escape code "\\uBEEF" in string`,
209 }, {
210 in: `"\uD800\udead"`, // valid surrogate pair
211 wantVal: V("𐊭"),
212 wantOut: `"𐊭"`,
213 }, {
214 in: `"\u0000\"\\\/\b\f\n\r\t"`,
215 wantVal: V("\u0000\"\\/\b\f\n\r\t"),
216 wantOut: `"\u0000\"\\/\b\f\n\r\t"`,
217 }, {
218 in: `-`,
219 wantErr: `invalid "-" as number`,
220 }, {
221 in: `-0`,
222 wantVal: V(math.Copysign(0, -1)),
223 wantOut: `-0`,
224 }, {
225 in: `+0`,
226 wantErr: `invalid "+0" as value`,
227 }, {
228 in: `-+`,
229 wantErr: `invalid "-+" as number`,
230 }, {
231 in: `0.`,
232 wantErr: `invalid "0." as number`,
233 }, {
234 in: `.1`,
235 wantErr: `invalid ".1" as value`,
236 }, {
237 in: `0.e1`,
238 wantErr: `invalid "0.e1" as number`,
239 }, {
240 in: `0.0`,
241 wantVal: V(0.0),
242 wantOut: "0",
243 }, {
244 in: `01`,
245 wantErr: `invalid "01" as number`,
246 }, {
247 in: `0e`,
248 wantErr: `invalid "0e" as number`,
249 }, {
250 in: `0e0`,
251 wantVal: V(0.0),
252 wantOut: "0",
253 }, {
254 in: `0E0`,
255 wantVal: V(0.0),
256 wantOut: "0",
257 }, {
258 in: `0Ee`,
259 wantErr: `invalid "0Ee" as number`,
260 }, {
261 in: `-1.0E+1`,
262 wantVal: V(-10.0),
263 wantOut: "-10",
264 }, {
265 in: `
266 {
267 "firstName" : "John",
268 "lastName" : "Smith" ,
269 "isAlive" : true,
270 "age" : 27,
271 "address" : {
272 "streetAddress" : "21 2nd Street" ,
273 "city" : "New York" ,
274 "state" : "NY" ,
275 "postalCode" : "10021-3100"
276 },
277 "phoneNumbers" : [
278 {
279 "type" : "home" ,
280 "number" : "212 555-1234"
281 } ,
282 {
283 "type" : "office" ,
284 "number" : "646 555-4567"
285 } ,
286 {
287 "type" : "mobile" ,
288 "number" : "123 456-7890"
289 }
290 ],
291 "children" : [] ,
292 "spouse" : null
293 }
294 `,
295 wantVal: V(Obj{
296 {V("firstName"), V("John")},
297 {V("lastName"), V("Smith")},
298 {V("isAlive"), V(true)},
299 {V("age"), V(27.0)},
300 {V("address"), V(Obj{
301 {V("streetAddress"), V("21 2nd Street")},
302 {V("city"), V("New York")},
303 {V("state"), V("NY")},
304 {V("postalCode"), V("10021-3100")},
305 })},
306 {V("phoneNumbers"), V(Arr{
307 V(Obj{
308 {V("type"), V("home")},
309 {V("number"), V("212 555-1234")},
310 }),
311 V(Obj{
312 {V("type"), V("office")},
313 {V("number"), V("646 555-4567")},
314 }),
315 V(Obj{
316 {V("type"), V("mobile")},
317 {V("number"), V("123 456-7890")},
318 }),
319 })},
320 {V("children"), V(Arr{})},
321 {V("spouse"), V(nil)},
322 }),
323 wantOut: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`,
324 wantOutIndent: `{
325 "firstName": "John",
326 "lastName": "Smith",
327 "isAlive": true,
328 "age": 27,
329 "address": {
330 "streetAddress": "21 2nd Street",
331 "city": "New York",
332 "state": "NY",
333 "postalCode": "10021-3100"
334 },
335 "phoneNumbers": [
336 {
337 "type": "home",
338 "number": "212 555-1234"
339 },
340 {
341 "type": "office",
342 "number": "646 555-4567"
343 },
344 {
345 "type": "mobile",
346 "number": "123 456-7890"
347 }
348 ],
349 "children": [],
350 "spouse": null
351}`,
352 }}
353
354 opts := cmp.Options{
355 cmpopts.EquateEmpty(),
356 cmp.Transformer("", func(v Value) interface{} {
357 switch v.typ {
358 case 0:
359 return nil // special case so Value{} == Value{}
360 case Null:
361 return nil
362 case Bool:
363 return v.Bool()
364 case Number:
365 return v.Number()
366 case String:
367 return v.String()
368 case Array:
369 return v.Array()
370 case Object:
371 return v.Object()
372 default:
373 panic("invalid type")
374 }
375 }),
376 }
377 for _, tt := range tests {
378 t.Run("", func(t *testing.T) {
379 if tt.in != "" || tt.wantVal.Type() != 0 || tt.wantErr != "" {
380 gotVal, err := Unmarshal([]byte(tt.in))
381 if err == nil {
382 if tt.wantErr != "" {
383 t.Errorf("Unmarshal(): got nil error, want %v", tt.wantErr)
384 }
385 } else {
386 if tt.wantErr == "" {
387 t.Errorf("Unmarshal(): got %v, want nil error", err)
388 } else if !strings.Contains(err.Error(), tt.wantErr) {
389 t.Errorf("Unmarshal(): got %v, want %v", err, tt.wantErr)
390 }
391 }
392 if diff := cmp.Diff(gotVal, tt.wantVal, opts); diff != "" {
393 t.Errorf("Unmarshal(): output mismatch (-got +want):\n%s", diff)
394 }
395 }
396 if tt.wantOut != "" {
397 gotOut, err := Marshal(tt.wantVal, "")
398 if err != nil {
399 t.Errorf("Marshal(): got %v, want nil error", err)
400 }
401 if string(gotOut) != tt.wantOut {
402 t.Errorf("Marshal():\ngot: %s\nwant: %s", gotOut, tt.wantOut)
403 }
404 }
405 if tt.wantOutIndent != "" {
406 gotOut, err := Marshal(tt.wantVal, "\t")
407 if err != nil {
408 t.Errorf("Marshal(Indent): got %v, want nil error", err)
409 }
410 if string(gotOut) != tt.wantOutIndent {
411 t.Errorf("Marshal(Indent):\ngot: %s\nwant: %s", gotOut, tt.wantOutIndent)
412 }
413 }
414 })
415 }
416}