Skip to content

Commit 707e879

Browse files
committed
feat(Descendant,WalkJSON): added pointer descendant method, experimental WalkJSON func
Descendant creates subpath pointer from a given pointer. WalkJSON is a work-in-progress
1 parent 5919095 commit 707e879

File tree

5 files changed

+219
-1
lines changed

5 files changed

+219
-1
lines changed

.circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
github.com/jstemmer/go-junit-report
2121
- run:
2222
name: Run Lint Tests
23-
command: golint ./...
23+
command: golint -set_exit_status ./...
2424
- run:
2525
name: Run Tests
2626
command: go test -v -race -coverprofile=coverage.txt -covermode=atomic

pointer.go

+18
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ func (p Pointer) Eval(data interface{}) (result interface{}, err error) {
8686
return
8787
}
8888

89+
// Descendant returns a new pointer to a descendant of the current pointer
90+
// parsing the input path into components
91+
func (p Pointer) Descendant(path string) (Pointer, error) {
92+
if !strings.HasPrefix(path, "/") {
93+
path = "/" + path
94+
}
95+
dpath, err := parse(path)
96+
if err != nil {
97+
return p, err
98+
}
99+
100+
if p.String() == "/" {
101+
return dpath, nil
102+
}
103+
104+
return append(p, dpath...), nil
105+
}
106+
89107
// Evaluation of each reference token begins by decoding any escaped
90108
// character sequence. This is performed by first transforming any
91109
// occurrence of the sequence '~1' to '/', and then transforming any

pointer_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ func TestParse(t *testing.T) {
5656
parsed string
5757
err string
5858
}{
59+
{"#/", "/", ""},
5960
{"#/foo", "/foo", ""},
61+
{"#/foo/", "/foo/", ""},
6062

6163
{"://", "", "parse ://: missing protocol scheme"},
6264
{"#7", "", "non-empty references must begin with a '/' character"},
@@ -148,6 +150,40 @@ func TestEval(t *testing.T) {
148150
}
149151
}
150152

153+
func TestDescendent(t *testing.T) {
154+
cases := []struct {
155+
parent string
156+
path string
157+
parsed string
158+
err string
159+
}{
160+
{"#/", "0", "/0", ""},
161+
{"/0", "0", "/0/0", ""},
162+
{"/foo", "0", "/foo/0", ""},
163+
{"/foo", "0", "/foo/0", ""},
164+
{"/foo/0", "0", "/foo/0/0", ""},
165+
}
166+
167+
for i, c := range cases {
168+
p, err := Parse(c.parent)
169+
if err != nil {
170+
t.Errorf("case %d error parsing parent: %s", i, err.Error())
171+
continue
172+
}
173+
174+
desc, err := p.Descendant(c.path)
175+
if !(err == nil && c.err == "" || err != nil && err.Error() == c.err) {
176+
t.Errorf("case %d error mismatch. expected: %s, got: %s", i, c.err, err)
177+
continue
178+
}
179+
180+
if desc.String() != c.parsed {
181+
t.Errorf("case %d: expected: %s, got: %s", i, c.parsed, desc.String())
182+
continue
183+
}
184+
}
185+
}
186+
151187
func BenchmarkEval(b *testing.B) {
152188
document := []byte(`{
153189
"foo": {

traversal.go

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package jsonpointer
2+
3+
import (
4+
"reflect"
5+
)
6+
7+
// JSONContainer returns any existing child value for a given JSON property string
8+
type JSONContainer interface {
9+
// JSONProp takes a string reference for a given JSON property.
10+
// implementations must return any matching property of that name,
11+
// nil if no such subproperty exists.
12+
// Note that implementations on slice-types are expected to convert
13+
// prop to an integer value
14+
JSONProp(prop string) interface{}
15+
}
16+
17+
// JSONParent is an interface that enables tree traversal by listing
18+
// all immediate children of an object
19+
type JSONParent interface {
20+
// JSONChildren should return all immidiate children of this element
21+
// with json property names as keys, go types as values
22+
// Note that implementations on slice-types are expected to convert
23+
// integers to string keys
24+
JSONProps() map[string]interface{}
25+
}
26+
27+
// WalkJSON calls visit on all elements in a tree of decoded json
28+
func WalkJSON(tree interface{}, visit func(elem interface{}) error) error {
29+
if tree == nil {
30+
return nil
31+
}
32+
33+
if err := visit(tree); err != nil {
34+
return err
35+
}
36+
37+
if con, ok := tree.(JSONParent); ok {
38+
for _, ch := range con.JSONProps() {
39+
if err := WalkJSON(ch, visit); err != nil {
40+
return err
41+
}
42+
}
43+
return nil
44+
}
45+
46+
// fast-path for common json types
47+
switch t := tree.(type) {
48+
case map[string]interface{}:
49+
for _, val := range t {
50+
if err := WalkJSON(val, visit); err != nil {
51+
return err
52+
}
53+
}
54+
return nil
55+
case []interface{}:
56+
for _, val := range t {
57+
if err := WalkJSON(val, visit); err != nil {
58+
return err
59+
}
60+
}
61+
return nil
62+
}
63+
64+
return walkValue(reflect.ValueOf(tree), visit)
65+
}
66+
67+
func walkValue(v reflect.Value, visit func(elem interface{}) error) error {
68+
switch v.Kind() {
69+
case reflect.Invalid:
70+
return nil
71+
case reflect.Ptr:
72+
if !v.IsNil() {
73+
walkValue(v.Elem(), visit)
74+
}
75+
case reflect.Map:
76+
for _, key := range v.MapKeys() {
77+
mi := v.MapIndex(key)
78+
if mi.CanInterface() {
79+
WalkJSON(mi.Interface(), visit)
80+
}
81+
}
82+
case reflect.Struct:
83+
// t := v.Type()
84+
// TypeOf returns the reflection Type that represents the dynamic type of variable.
85+
// If variable is a nil interface value, TypeOf returns nil.
86+
for i := 0; i < v.NumField(); i++ {
87+
f := v.Field(i)
88+
// fmt.Printf("%d: %s %s %s = %v\n", i, t.Field(i).Name, f.Type(), t.Field(i).Tag.Get("json"), f.CanInterface())
89+
if f.CanInterface() {
90+
WalkJSON(f.Interface(), visit)
91+
}
92+
}
93+
case reflect.Slice, reflect.Array:
94+
for i := 0; i < v.Len(); i++ {
95+
WalkJSON(v.Index(i).Interface(), visit)
96+
}
97+
}
98+
return nil
99+
}

traversal_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package jsonpointer
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
)
7+
8+
type A struct {
9+
Foo string `json:"$foo,omitempty"`
10+
Foo2 bool `json:"$foo2"`
11+
Bar B
12+
}
13+
14+
type B struct {
15+
nope string
16+
Baz int
17+
C C
18+
D D
19+
}
20+
21+
type C struct {
22+
Nope string
23+
}
24+
25+
func (c C) JSONProps() map[string]interface{} {
26+
return map[string]interface{}{
27+
"bat": "book",
28+
"stuff": false,
29+
"other": nil,
30+
}
31+
}
32+
33+
type D []string
34+
35+
var data = []byte(`{
36+
"$foo" : "fooval",
37+
"$foo2" : true,
38+
"bar" : {
39+
"baz" : 1,
40+
"C" : {
41+
"won't" : "register"
42+
}
43+
}
44+
}`)
45+
46+
func TestWalkJSON(t *testing.T) {
47+
a := &A{}
48+
if err := json.Unmarshal(data, a); err != nil {
49+
t.Errorf("unexpected unmarshal error: %s", err.Error())
50+
return
51+
}
52+
53+
elements := 0
54+
expectElements := 9
55+
WalkJSON(a, func(elem interface{}) error {
56+
t.Logf("%#v", elem)
57+
elements++
58+
return nil
59+
})
60+
61+
if elements != expectElements {
62+
t.Errorf("expected %d elements, got: %d", expectElements, elements)
63+
}
64+
65+
}

0 commit comments

Comments
 (0)