1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package jsonpointer
27
28 import (
29 "encoding/json"
30 "fmt"
31 "strconv"
32 "testing"
33
34 "github.com/stretchr/testify/assert"
35 "github.com/stretchr/testify/require"
36 )
37
38 const (
39 TestDocumentNBItems = 11
40 TestNodeObjNBItems = 4
41 TestDocumentString = `{
42 "foo": ["bar", "baz"],
43 "obj": { "a":1, "b":2, "c":[3,4], "d":[ {"e":9}, {"f":[50,51]} ] },
44 "": 0,
45 "a/b": 1,
46 "c%d": 2,
47 "e^f": 3,
48 "g|h": 4,
49 "i\\j": 5,
50 "k\"l": 6,
51 " ": 7,
52 "m~n": 8
53 }`
54 )
55
56 var testDocumentJSON any
57
58 type testStructJSON struct {
59 Foo []string `json:"foo"`
60 Obj struct {
61 A int `json:"a"`
62 B int `json:"b"`
63 C []int `json:"c"`
64 D []struct {
65 E int `json:"e"`
66 F []int `json:"f"`
67 } `json:"d"`
68 } `json:"obj"`
69 }
70
71 type aliasedMap map[string]any
72
73 var testStructJSONDoc testStructJSON
74 var testStructJSONPtr *testStructJSON
75
76 func init() {
77 if err := json.Unmarshal([]byte(TestDocumentString), &testDocumentJSON); err != nil {
78 panic(err)
79 }
80 if err := json.Unmarshal([]byte(TestDocumentString), &testStructJSONDoc); err != nil {
81 panic(err)
82 }
83
84 testStructJSONPtr = &testStructJSONDoc
85 }
86
87 func TestEscaping(t *testing.T) {
88 ins := []string{`/`, `/`, `/a~1b`, `/a~1b`, `/c%d`, `/e^f`, `/g|h`, `/i\j`, `/k"l`, `/ `, `/m~0n`}
89 outs := []float64{0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8}
90
91 for i := range ins {
92 p, err := New(ins[i])
93 require.NoError(t, err, "input: %v", ins[i])
94 result, _, err := p.Get(testDocumentJSON)
95
96 require.NoError(t, err, "input: %v", ins[i])
97 assert.InDeltaf(t, outs[i], result, 1e-6, "input: %v", ins[i])
98 }
99
100 }
101
102 func TestFullDocument(t *testing.T) {
103 const in = ``
104
105 p, err := New(in)
106 require.NoErrorf(t, err, "New(%v) error %v", in, err)
107
108 result, _, err := p.Get(testDocumentJSON)
109 require.NoErrorf(t, err, "Get(%v) error %v", in, err)
110
111 asMap, ok := result.(map[string]any)
112 require.True(t, ok)
113 require.Lenf(t, asMap, TestDocumentNBItems, "Get(%v) = %v, expect full document", in, result)
114
115 result, _, err = p.get(testDocumentJSON, nil)
116 require.NoErrorf(t, err, "Get(%v) error %v", in, err)
117
118 asMap, ok = result.(map[string]any)
119 require.True(t, ok)
120 require.Lenf(t, asMap, TestDocumentNBItems, "Get(%v) = %v, expect full document", in, result)
121 }
122
123 func TestDecodedTokens(t *testing.T) {
124 p, err := New("/obj/a~1b")
125 require.NoError(t, err)
126 assert.Equal(t, []string{"obj", "a/b"}, p.DecodedTokens())
127 }
128
129 func TestIsEmpty(t *testing.T) {
130 p, err := New("")
131 require.NoError(t, err)
132
133 assert.True(t, p.IsEmpty())
134 p, err = New("/obj")
135 require.NoError(t, err)
136
137 assert.False(t, p.IsEmpty())
138 }
139
140 func TestGetSingle(t *testing.T) {
141 const in = `/obj`
142
143 t.Run("should create a new JSON pointer", func(t *testing.T) {
144 _, err := New(in)
145 require.NoError(t, err)
146 })
147
148 t.Run(`should find token "obj" in JSON`, func(t *testing.T) {
149 result, _, err := GetForToken(testDocumentJSON, "obj")
150 require.NoError(t, err)
151 assert.Len(t, result, TestNodeObjNBItems)
152 })
153
154 t.Run(`should find token "obj" in type alias interface`, func(t *testing.T) {
155 type alias interface{}
156 var in alias = testDocumentJSON
157 result, _, err := GetForToken(in, "obj")
158 require.NoError(t, err)
159 assert.Len(t, result, TestNodeObjNBItems)
160 })
161
162 t.Run(`should find token "obj" in pointer to interface`, func(t *testing.T) {
163 in := &testDocumentJSON
164 result, _, err := GetForToken(in, "obj")
165 require.NoError(t, err)
166 assert.Len(t, result, TestNodeObjNBItems)
167 })
168
169 t.Run(`should not find token "Obj" in struct`, func(t *testing.T) {
170 result, _, err := GetForToken(testStructJSONDoc, "Obj")
171 require.Error(t, err)
172 assert.Nil(t, result)
173 })
174
175 t.Run(`should not find token "Obj2" in struct`, func(t *testing.T) {
176 result, _, err := GetForToken(testStructJSONDoc, "Obj2")
177 require.Error(t, err)
178 assert.Nil(t, result)
179 })
180
181 t.Run(`should not find token in nil`, func(t *testing.T) {
182 result, _, err := GetForToken(nil, "obj")
183 require.Error(t, err)
184 assert.Nil(t, result)
185 })
186
187 t.Run(`should not find token in nil interface`, func(t *testing.T) {
188 var in interface{}
189 result, _, err := GetForToken(in, "obj")
190 require.Error(t, err)
191 assert.Nil(t, result)
192 })
193 }
194
195 type pointableImpl struct {
196 a string
197 }
198
199 func (p pointableImpl) JSONLookup(token string) (any, error) {
200 if token == "some" {
201 return p.a, nil
202 }
203 return nil, fmt.Errorf("object has no field %q", token)
204 }
205
206 type pointableMap map[string]string
207
208 func (p pointableMap) JSONLookup(token string) (any, error) {
209 if token == "swap" {
210 return p["swapped"], nil
211 }
212
213 v, ok := p[token]
214 if ok {
215 return v, nil
216 }
217
218 return nil, fmt.Errorf("object has no key %q", token)
219 }
220
221 func TestPointableInterface(t *testing.T) {
222 p := &pointableImpl{"hello"}
223
224 result, _, err := GetForToken(p, "some")
225 require.NoError(t, err)
226 assert.Equal(t, p.a, result)
227
228 result, _, err = GetForToken(p, "something")
229 require.Error(t, err)
230 assert.Nil(t, result)
231
232 pm := pointableMap{"swapped": "hello", "a": "world"}
233 result, _, err = GetForToken(pm, "swap")
234 require.NoError(t, err)
235 assert.Equal(t, pm["swapped"], result)
236
237 result, _, err = GetForToken(pm, "a")
238 require.NoError(t, err)
239 assert.Equal(t, pm["a"], result)
240 }
241
242 func TestGetNode(t *testing.T) {
243 const in = `/obj`
244
245 p, err := New(in)
246 require.NoError(t, err)
247
248 result, _, err := p.Get(testDocumentJSON)
249 require.NoError(t, err)
250 assert.Len(t, result, TestNodeObjNBItems)
251
252 result, _, err = p.Get(aliasedMap(testDocumentJSON.(map[string]any)))
253 require.NoError(t, err)
254 assert.Len(t, result, TestNodeObjNBItems)
255
256 result, _, err = p.Get(testStructJSONDoc)
257 require.NoError(t, err)
258 assert.Equal(t, testStructJSONDoc.Obj, result)
259
260 result, _, err = p.Get(testStructJSONPtr)
261 require.NoError(t, err)
262 assert.Equal(t, testStructJSONDoc.Obj, result)
263 }
264
265 func TestArray(t *testing.T) {
266 ins := []string{`/foo/0`, `/foo/0`, `/foo/1`}
267 outs := []string{"bar", "bar", "baz"}
268
269 for i := range ins {
270 p, err := New(ins[i])
271 require.NoError(t, err)
272
273 result, _, err := p.Get(testStructJSONDoc)
274 require.NoError(t, err)
275 assert.Equal(t, outs[i], result)
276
277 result, _, err = p.Get(testStructJSONPtr)
278 require.NoError(t, err)
279 assert.Equal(t, outs[i], result)
280
281 result, _, err = p.Get(testDocumentJSON)
282 require.NoError(t, err)
283 assert.Equal(t, outs[i], result)
284 }
285 }
286
287 func TestOtherThings(t *testing.T) {
288 _, err := New("abc")
289 require.Error(t, err)
290
291 p, err := New("")
292 require.NoError(t, err)
293 assert.Equal(t, "", p.String())
294
295 p, err = New("/obj/a")
296 require.NoError(t, err)
297 assert.Equal(t, "/obj/a", p.String())
298
299 s := Escape("m~n")
300 assert.Equal(t, "m~0n", s)
301 s = Escape("m/n")
302 assert.Equal(t, "m~1n", s)
303
304 p, err = New("/foo/3")
305 require.NoError(t, err)
306 _, _, err = p.Get(testDocumentJSON)
307 require.Error(t, err)
308
309 p, err = New("/foo/a")
310 require.NoError(t, err)
311 _, _, err = p.Get(testDocumentJSON)
312 require.Error(t, err)
313
314 p, err = New("/notthere")
315 require.NoError(t, err)
316 _, _, err = p.Get(testDocumentJSON)
317 require.Error(t, err)
318
319 p, err = New("/invalid")
320 require.NoError(t, err)
321 _, _, err = p.Get(1234)
322 require.Error(t, err)
323
324 p, err = New("/foo/1")
325 require.NoError(t, err)
326 expected := "hello"
327 bbb := testDocumentJSON.(map[string]any)["foo"]
328 bbb.([]any)[1] = "hello"
329
330 v, _, err := p.Get(testDocumentJSON)
331 require.NoError(t, err)
332 assert.Equal(t, expected, v)
333
334 esc := Escape("a/")
335 assert.Equal(t, "a~1", esc)
336 unesc := Unescape(esc)
337 assert.Equal(t, "a/", unesc)
338
339 unesc = Unescape("~01")
340 assert.Equal(t, "~1", unesc)
341 assert.Equal(t, "~0~1", Escape("~/"))
342 assert.Equal(t, "~/", Unescape("~0~1"))
343 }
344
345 func TestObject(t *testing.T) {
346 ins := []string{`/obj/a`, `/obj/b`, `/obj/c/0`, `/obj/c/1`, `/obj/c/1`, `/obj/d/1/f/0`}
347 outs := []float64{1, 2, 3, 4, 4, 50}
348
349 for i := range ins {
350 p, err := New(ins[i])
351 require.NoError(t, err)
352
353 result, _, err := p.Get(testDocumentJSON)
354 require.NoError(t, err)
355 assert.InDelta(t, outs[i], result, 1e-6)
356
357 result, _, err = p.Get(testStructJSONDoc)
358 require.NoError(t, err)
359 assert.InDelta(t, outs[i], result, 1e-6)
360
361 result, _, err = p.Get(testStructJSONPtr)
362 require.NoError(t, err)
363 assert.InDelta(t, outs[i], result, 1e-6)
364 }
365 }
366
367
373 type setJSONDoc struct {
374 A []struct {
375 B int `json:"b"`
376 C int `json:"c"`
377 } `json:"a"`
378 D int `json:"d"`
379 }
380
381 type settableDoc struct {
382 Coll settableColl
383 Int settableInt
384 }
385
386 func (s settableDoc) MarshalJSON() ([]byte, error) {
387 var res struct {
388 A settableColl `json:"a"`
389 D settableInt `json:"d"`
390 }
391 res.A = s.Coll
392 res.D = s.Int
393 return json.Marshal(res)
394 }
395 func (s *settableDoc) UnmarshalJSON(data []byte) error {
396 var res struct {
397 A settableColl `json:"a"`
398 D settableInt `json:"d"`
399 }
400
401 if err := json.Unmarshal(data, &res); err != nil {
402 return err
403 }
404 s.Coll = res.A
405 s.Int = res.D
406 return nil
407 }
408
409
410 func (s settableDoc) JSONLookup(token string) (any, error) {
411 switch token {
412 case "a":
413 return &s.Coll, nil
414 case "d":
415 return &s.Int, nil
416 default:
417 return nil, fmt.Errorf("%s is not a known field", token)
418 }
419 }
420
421
422 func (s *settableDoc) JSONSet(token string, data any) error {
423 switch token {
424 case "a":
425 switch dt := data.(type) {
426 case settableColl:
427 s.Coll = dt
428 return nil
429 case *settableColl:
430 if dt != nil {
431 s.Coll = *dt
432 } else {
433 s.Coll = settableColl{}
434 }
435 return nil
436 case []settableCollItem:
437 s.Coll.Items = dt
438 return nil
439 }
440 case "d":
441 switch dt := data.(type) {
442 case settableInt:
443 s.Int = dt
444 return nil
445 case int:
446 s.Int.Value = dt
447 return nil
448 case int8:
449 s.Int.Value = int(dt)
450 return nil
451 case int16:
452 s.Int.Value = int(dt)
453 return nil
454 case int32:
455 s.Int.Value = int(dt)
456 return nil
457 case int64:
458 s.Int.Value = int(dt)
459 return nil
460 default:
461 return fmt.Errorf("invalid type %T for %s", data, token)
462 }
463 }
464 return fmt.Errorf("%s is not a known field", token)
465 }
466
467 type settableColl struct {
468 Items []settableCollItem
469 }
470
471 func (s settableColl) MarshalJSON() ([]byte, error) {
472 return json.Marshal(s.Items)
473 }
474 func (s *settableColl) UnmarshalJSON(data []byte) error {
475 return json.Unmarshal(data, &s.Items)
476 }
477
478
479 func (s settableColl) JSONLookup(token string) (any, error) {
480 if tok, err := strconv.Atoi(token); err == nil {
481 return &s.Items[tok], nil
482 }
483 return nil, fmt.Errorf("%s is not a valid index", token)
484 }
485
486
487 func (s *settableColl) JSONSet(token string, data any) error {
488 if _, err := strconv.Atoi(token); err == nil {
489 _, err := SetForToken(s.Items, token, data)
490 return err
491 }
492 return fmt.Errorf("%s is not a valid index", token)
493 }
494
495 type settableCollItem struct {
496 B int `json:"b"`
497 C int `json:"c"`
498 }
499
500 type settableInt struct {
501 Value int
502 }
503
504 func (s settableInt) MarshalJSON() ([]byte, error) {
505 return json.Marshal(s.Value)
506 }
507 func (s *settableInt) UnmarshalJSON(data []byte) error {
508 return json.Unmarshal(data, &s.Value)
509 }
510
511 func TestSetNode(t *testing.T) {
512 const jsonText = `{"a":[{"b": 1, "c": 2}], "d": 3}`
513
514 var jsonDocument any
515 require.NoError(t, json.Unmarshal([]byte(jsonText), &jsonDocument))
516
517 t.Run("with set node c", func(t *testing.T) {
518 const in = "/a/0/c"
519 p, err := New(in)
520 require.NoError(t, err)
521
522 _, err = p.Set(jsonDocument, 999)
523 require.NoError(t, err)
524
525 firstNode, ok := jsonDocument.(map[string]any)
526 require.True(t, ok)
527 assert.Len(t, firstNode, 2)
528
529 sliceNode, ok := firstNode["a"].([]any)
530 require.True(t, ok)
531 assert.Len(t, sliceNode, 1)
532
533 changedNode, ok := sliceNode[0].(map[string]any)
534 require.True(t, ok)
535 chNodeVI := changedNode["c"]
536
537 require.IsType(t, 0, chNodeVI)
538 changedNodeValue := chNodeVI.(int)
539 require.Equal(t, 999, changedNodeValue)
540 assert.Len(t, sliceNode, 1)
541 })
542
543 t.Run("with set node 0 with map", func(t *testing.T) {
544 v, err := New("/a/0")
545 require.NoError(t, err)
546
547 _, err = v.Set(jsonDocument, map[string]any{"b": 3, "c": 8})
548 require.NoError(t, err)
549
550 firstNode, ok := jsonDocument.(map[string]any)
551 require.True(t, ok)
552 assert.Len(t, firstNode, 2)
553
554 sliceNode, ok := firstNode["a"].([]any)
555 require.True(t, ok)
556 assert.Len(t, sliceNode, 1)
557
558 changedNode, ok := sliceNode[0].(map[string]any)
559 require.True(t, ok)
560 assert.Equal(t, 3, changedNode["b"])
561 assert.Equal(t, 8, changedNode["c"])
562 })
563
564 t.Run("with struct", func(t *testing.T) {
565 var structDoc setJSONDoc
566 require.NoError(t, json.Unmarshal([]byte(jsonText), &structDoc))
567
568 t.Run("with set array node", func(t *testing.T) {
569 g, err := New("/a")
570 require.NoError(t, err)
571
572 _, err = g.Set(&structDoc, []struct {
573 B int `json:"b"`
574 C int `json:"c"`
575 }{{B: 4, C: 7}})
576 require.NoError(t, err)
577 assert.Len(t, structDoc.A, 1)
578 changedNode := structDoc.A[0]
579 assert.Equal(t, 4, changedNode.B)
580 assert.Equal(t, 7, changedNode.C)
581 })
582
583 t.Run("with set node 0 with struct", func(t *testing.T) {
584 v, err := New("/a/0")
585 require.NoError(t, err)
586
587 _, err = v.Set(structDoc, struct {
588 B int `json:"b"`
589 C int `json:"c"`
590 }{B: 3, C: 8})
591 require.NoError(t, err)
592 assert.Len(t, structDoc.A, 1)
593 changedNode := structDoc.A[0]
594 assert.Equal(t, 3, changedNode.B)
595 assert.Equal(t, 8, changedNode.C)
596 })
597
598 t.Run("with set node c with struct", func(t *testing.T) {
599 p, err := New("/a/0/c")
600 require.NoError(t, err)
601
602 _, err = p.Set(&structDoc, 999)
603 require.NoError(t, err)
604
605 require.Len(t, structDoc.A, 1)
606 assert.Equal(t, 999, structDoc.A[0].C)
607 })
608 })
609
610 t.Run("with Settable", func(t *testing.T) {
611 var setDoc settableDoc
612 require.NoError(t, json.Unmarshal([]byte(jsonText), &setDoc))
613
614 t.Run("with array node a", func(t *testing.T) {
615 g, err := New("/a")
616 require.NoError(t, err)
617
618 _, err = g.Set(&setDoc, []settableCollItem{{B: 4, C: 7}})
619 require.NoError(t, err)
620 assert.Len(t, setDoc.Coll.Items, 1)
621 changedNode := setDoc.Coll.Items[0]
622 assert.Equal(t, 4, changedNode.B)
623 assert.Equal(t, 7, changedNode.C)
624 })
625
626 t.Run("with node 0", func(t *testing.T) {
627 v, err := New("/a/0")
628 require.NoError(t, err)
629
630 _, err = v.Set(setDoc, settableCollItem{B: 3, C: 8})
631 require.NoError(t, err)
632 assert.Len(t, setDoc.Coll.Items, 1)
633 changedNode := setDoc.Coll.Items[0]
634 assert.Equal(t, 3, changedNode.B)
635 assert.Equal(t, 8, changedNode.C)
636 })
637
638 t.Run("with node c", func(t *testing.T) {
639 p, err := New("/a/0/c")
640 require.NoError(t, err)
641 _, err = p.Set(setDoc, 999)
642 require.NoError(t, err)
643 require.Len(t, setDoc.Coll.Items, 1)
644 assert.Equal(t, 999, setDoc.Coll.Items[0].C)
645 })
646 })
647 }
648
649 func TestOffset(t *testing.T) {
650 cases := []struct {
651 name string
652 ptr string
653 input string
654 offset int64
655 hasError bool
656 }{
657 {
658 name: "object key",
659 ptr: "/foo/bar",
660 input: `{"foo": {"bar": 21}}`,
661 offset: 9,
662 },
663 {
664 name: "complex object key",
665 ptr: "/paths/~1p~1{}/get",
666 input: `{"paths": {"foo": {"bar": 123, "baz": {}}, "/p/{}": {"get": {}}}}`,
667 offset: 53,
668 },
669 {
670 name: "array index",
671 ptr: "/0/1",
672 input: `[[1,2], [3,4]]`,
673 offset: 3,
674 },
675 {
676 name: "mix array index and object key",
677 ptr: "/0/1/foo/0",
678 input: `[[1, {"foo": ["a", "b"]}], [3, 4]]`,
679 offset: 14,
680 },
681 {
682 name: "nonexist object key",
683 ptr: "/foo/baz",
684 input: `{"foo": {"bar": 21}}`,
685 hasError: true,
686 },
687 {
688 name: "nonexist array index",
689 ptr: "/0/2",
690 input: `[[1,2], [3,4]]`,
691 hasError: true,
692 },
693 }
694
695 for _, tt := range cases {
696 t.Run(tt.name, func(t *testing.T) {
697 ptr, err := New(tt.ptr)
698 require.NoError(t, err)
699
700 offset, err := ptr.Offset(tt.input)
701 if tt.hasError {
702 require.Error(t, err)
703 return
704 }
705
706 t.Log(offset, err)
707 require.NoError(t, err)
708 assert.Equal(t, tt.offset, offset)
709 })
710 }
711 }
712
View as plain text