Skip to content

Commit a603eaf

Browse files
authored
Merge pull request #10 from magodo/offset
New method `Offset` to `jsonpointer.Pointer` to return the offset
2 parents 1539753 + d3f17d3 commit a603eaf

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

pointer.go

+123
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
package jsonpointer
2727

2828
import (
29+
"encoding/json"
2930
"errors"
3031
"fmt"
3132
"reflect"
@@ -40,6 +41,7 @@ const (
4041
pointerSeparator = `/`
4142

4243
invalidStart = `JSON pointer must be empty or start with a "` + pointerSeparator
44+
notFound = `Can't find the pointer in the document`
4345
)
4446

4547
var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem()
@@ -361,6 +363,127 @@ func (p *Pointer) String() string {
361363
return pointerString
362364
}
363365

366+
func (p *Pointer) Offset(document string) (int64, error) {
367+
dec := json.NewDecoder(strings.NewReader(document))
368+
var offset int64
369+
for _, ttk := range p.DecodedTokens() {
370+
tk, err := dec.Token()
371+
if err != nil {
372+
return 0, err
373+
}
374+
switch tk := tk.(type) {
375+
case json.Delim:
376+
switch tk {
377+
case '{':
378+
offset, err = offsetSingleObject(dec, ttk)
379+
if err != nil {
380+
return 0, err
381+
}
382+
case '[':
383+
offset, err = offsetSingleArray(dec, ttk)
384+
if err != nil {
385+
return 0, err
386+
}
387+
default:
388+
return 0, fmt.Errorf("invalid token %#v", tk)
389+
}
390+
default:
391+
return 0, fmt.Errorf("invalid token %#v", tk)
392+
}
393+
}
394+
return offset, nil
395+
}
396+
397+
func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
398+
for dec.More() {
399+
offset := dec.InputOffset()
400+
tk, err := dec.Token()
401+
if err != nil {
402+
return 0, err
403+
}
404+
switch tk := tk.(type) {
405+
case json.Delim:
406+
switch tk {
407+
case '{':
408+
if err := drainSingle(dec); err != nil {
409+
return 0, err
410+
}
411+
case '[':
412+
if err := drainSingle(dec); err != nil {
413+
return 0, err
414+
}
415+
}
416+
case string:
417+
if tk == decodedToken {
418+
return offset, nil
419+
}
420+
default:
421+
return 0, fmt.Errorf("invalid token %#v", tk)
422+
}
423+
}
424+
return 0, fmt.Errorf("token reference %q not found", decodedToken)
425+
}
426+
427+
func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
428+
idx, err := strconv.Atoi(decodedToken)
429+
if err != nil {
430+
return 0, fmt.Errorf("token reference %q is not a number: %v", decodedToken, err)
431+
}
432+
var i int
433+
for i = 0; i < idx && dec.More(); i++ {
434+
tk, err := dec.Token()
435+
if err != nil {
436+
return 0, err
437+
}
438+
switch tk := tk.(type) {
439+
case json.Delim:
440+
switch tk {
441+
case '{':
442+
if err := drainSingle(dec); err != nil {
443+
return 0, err
444+
}
445+
case '[':
446+
if err := drainSingle(dec); err != nil {
447+
return 0, err
448+
}
449+
}
450+
}
451+
}
452+
if !dec.More() {
453+
return 0, fmt.Errorf("token reference %q not found", decodedToken)
454+
}
455+
return dec.InputOffset(), nil
456+
}
457+
458+
// drainSingle drains a single level of object or array.
459+
// The decoder has to guarantee the begining delim (i.e. '{' or '[') has been consumed.
460+
func drainSingle(dec *json.Decoder) error {
461+
for dec.More() {
462+
tk, err := dec.Token()
463+
if err != nil {
464+
return err
465+
}
466+
switch tk := tk.(type) {
467+
case json.Delim:
468+
switch tk {
469+
case '{':
470+
if err := drainSingle(dec); err != nil {
471+
return err
472+
}
473+
case '[':
474+
if err := drainSingle(dec); err != nil {
475+
return err
476+
}
477+
}
478+
}
479+
}
480+
// Consumes the ending delim
481+
if _, err := dec.Token(); err != nil {
482+
return err
483+
}
484+
return nil
485+
}
486+
364487
// Specific JSON pointer encoding here
365488
// ~0 => ~
366489
// ~1 => /

pointer_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -595,3 +595,65 @@ func TestSetNode(t *testing.T) {
595595
}
596596
}
597597
}
598+
599+
func TestOffset(t *testing.T) {
600+
cases := []struct {
601+
name string
602+
ptr string
603+
input string
604+
offset int64
605+
hasError bool
606+
}{
607+
{
608+
name: "object key",
609+
ptr: "/foo/bar",
610+
input: `{"foo": {"bar": 21}}`,
611+
offset: 9,
612+
},
613+
{
614+
name: "complex object key",
615+
ptr: "/paths/~1p~1{}/get",
616+
input: `{"paths": {"foo": {"bar": 123, "baz": {}}, "/p/{}": {"get": {}}}}`,
617+
offset: 53,
618+
},
619+
{
620+
name: "array index",
621+
ptr: "/0/1",
622+
input: `[[1,2], [3,4]]`,
623+
offset: 3,
624+
},
625+
{
626+
name: "mix array index and object key",
627+
ptr: "/0/1/foo/0",
628+
input: `[[1, {"foo": ["a", "b"]}], [3, 4]]`,
629+
offset: 14,
630+
},
631+
{
632+
name: "nonexist object key",
633+
ptr: "/foo/baz",
634+
input: `{"foo": {"bar": 21}}`,
635+
hasError: true,
636+
},
637+
{
638+
name: "nonexist array index",
639+
ptr: "/0/2",
640+
input: `[[1,2], [3,4]]`,
641+
hasError: true,
642+
},
643+
}
644+
645+
for _, tt := range cases {
646+
t.Run(tt.name, func(t *testing.T) {
647+
ptr, err := New(tt.ptr)
648+
assert.NoError(t, err)
649+
offset, err := ptr.Offset(tt.input)
650+
if tt.hasError {
651+
assert.Error(t, err)
652+
return
653+
}
654+
t.Log(offset, err)
655+
assert.NoError(t, err)
656+
assert.Equal(t, tt.offset, offset)
657+
})
658+
}
659+
}

0 commit comments

Comments
 (0)