Enable path walking with a callback function
This commit is contained in:
parent
fd540a5237
commit
6bf826c466
2 changed files with 280 additions and 0 deletions
|
@ -723,3 +723,148 @@ func (d *Doc) Dec(value interface{}, path ...string) (*Doc, error) {
|
||||||
return &Doc{nil}, fmt.Errorf("Not possible to decrement.")
|
return &Doc{nil}, fmt.Errorf("Not possible to decrement.")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type walker func(key string, val interface{}) error
|
||||||
|
|
||||||
|
// Walk walks the value or values at a specified path.
|
||||||
|
func (d *Doc) Walk(exec walker, path ...string) error {
|
||||||
|
|
||||||
|
return d.walk(exec, nil, path...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Doc) join(parts ...[]string) string {
|
||||||
|
var path []string
|
||||||
|
for _, part := range parts {
|
||||||
|
path = append(path, part...)
|
||||||
|
}
|
||||||
|
return strings.Join(path, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Doc) walk(exec walker, prev []string, path ...string) error {
|
||||||
|
|
||||||
|
path = d.path(path...)
|
||||||
|
|
||||||
|
if len(path) == 0 {
|
||||||
|
d.data = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value found at the current
|
||||||
|
// path part is undefined, then ensure
|
||||||
|
// that it is an object
|
||||||
|
|
||||||
|
if d.data == nil {
|
||||||
|
d.data = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the temporary object so
|
||||||
|
// that we can loop over and traverse
|
||||||
|
// down the path parts of the data
|
||||||
|
|
||||||
|
object := d.data
|
||||||
|
|
||||||
|
// Loop over each part of the path
|
||||||
|
// whilst detecting if the data at
|
||||||
|
// the current path is an {} or []
|
||||||
|
|
||||||
|
for k, p := range path {
|
||||||
|
|
||||||
|
// If the value found at the current
|
||||||
|
// path part is an object, then move
|
||||||
|
// to the next part of the path
|
||||||
|
|
||||||
|
if m, ok := object.(map[string]interface{}); ok {
|
||||||
|
if object, ok = m[p]; !ok {
|
||||||
|
return exec(d.join(prev, path), nil)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value found at the current
|
||||||
|
// path part is an array, then perform
|
||||||
|
// the query on the specified items
|
||||||
|
|
||||||
|
if a, ok := object.([]interface{}); ok {
|
||||||
|
|
||||||
|
var i int
|
||||||
|
var e error
|
||||||
|
|
||||||
|
if p == "*" {
|
||||||
|
e = errors.New("")
|
||||||
|
} else if p == "first" {
|
||||||
|
i = 0
|
||||||
|
} else if p == "last" {
|
||||||
|
i = len(a) - 1
|
||||||
|
} else {
|
||||||
|
i, e = strconv.Atoi(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the path part is a numeric index
|
||||||
|
// then run the query on the specified
|
||||||
|
// index of the current data array
|
||||||
|
|
||||||
|
if e == nil {
|
||||||
|
|
||||||
|
if 0 == len(a) || i >= len(a) {
|
||||||
|
return fmt.Errorf("No item with index %d in array, using path %s", i, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if k == len(path)-1 {
|
||||||
|
return exec(d.join(prev, path), a[i])
|
||||||
|
} else {
|
||||||
|
var keep []string
|
||||||
|
keep = append(keep, prev...)
|
||||||
|
keep = append(keep, path[:k]...)
|
||||||
|
keep = append(keep, fmt.Sprintf("%d", i))
|
||||||
|
return Consume(a[i]).walk(exec, keep, path[k+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the path part is an asterisk
|
||||||
|
// then run the query on all of the
|
||||||
|
// items in the current data array
|
||||||
|
|
||||||
|
if p == "*" {
|
||||||
|
|
||||||
|
for i := len(a) - 1; i >= 0; i-- {
|
||||||
|
|
||||||
|
if k == len(path)-1 {
|
||||||
|
var keep []string
|
||||||
|
keep = append(keep, prev...)
|
||||||
|
keep = append(keep, path[:k]...)
|
||||||
|
keep = append(keep, fmt.Sprintf("%d", i))
|
||||||
|
if err := exec(d.join(keep), a[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var keep []string
|
||||||
|
keep = append(keep, prev...)
|
||||||
|
keep = append(keep, path[:k]...)
|
||||||
|
keep = append(keep, fmt.Sprintf("%d", i))
|
||||||
|
if err := Consume(a[i]).walk(exec, keep, path[k+1:]...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current path item is not an object or an array
|
||||||
|
// but there are still other items in the search path.
|
||||||
|
|
||||||
|
return fmt.Errorf("Can not get path %s from %v", path, object)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return exec(d.join(prev, path), object)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -783,6 +784,140 @@ func TestOperations(t *testing.T) {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
tmp := []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"test": "one",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"test": "two",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"test": "tre",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Convey("Can del array", t, func() {
|
||||||
|
err := doc.Del("the.item.arrays")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk nil", t, func() {
|
||||||
|
doc.Walk(func(key string, val interface{}) error {
|
||||||
|
doc.Set(tmp, "none")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
So(doc.Exists("none"), ShouldBeFalse)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array", t, func() {
|
||||||
|
doc.Walk(func(key string, val interface{}) error {
|
||||||
|
So(key, ShouldResemble, "the.item.arrays")
|
||||||
|
doc.Set(tmp, key)
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays")
|
||||||
|
So(doc.Get("the.item.arrays").Data(), ShouldResemble, tmp)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → *", t, func() {
|
||||||
|
doc.Walk(func(key string, val interface{}) error {
|
||||||
|
So(key, ShouldBeIn, "the.item.arrays.0", "the.item.arrays.1", "the.item.arrays.2")
|
||||||
|
So(val, ShouldBeIn, tmp[0], tmp[1], tmp[2])
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.*")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → * → object", t, func() {
|
||||||
|
doc.Walk(func(key string, val interface{}) error {
|
||||||
|
So(key, ShouldBeIn, "the.item.arrays.0.test", "the.item.arrays.1.test", "the.item.arrays.2.test")
|
||||||
|
So(val, ShouldBeIn, "one", "two", "tre")
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.*.test")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → first → object", t, func() {
|
||||||
|
doc.Walk(func(key string, val interface{}) error {
|
||||||
|
So(key, ShouldResemble, "the.item.arrays.0.test")
|
||||||
|
So(val, ShouldResemble, "one")
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.first.test")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → last → object", t, func() {
|
||||||
|
doc.Walk(func(key string, val interface{}) error {
|
||||||
|
So(key, ShouldResemble, "the.item.arrays.2.test")
|
||||||
|
So(val, ShouldResemble, "tre")
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.last.test")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → 0 → value", t, func() {
|
||||||
|
doc.Walk(func(key string, val interface{}) error {
|
||||||
|
So(key, ShouldResemble, "the.item.arrays.0")
|
||||||
|
So(val, ShouldResemble, map[string]interface{}{"test": "one"})
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.0")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → 1 → value", t, func() {
|
||||||
|
doc.Walk(func(key string, val interface{}) error {
|
||||||
|
So(key, ShouldResemble, "the.item.arrays.1")
|
||||||
|
So(val, ShouldResemble, map[string]interface{}{"test": "two"})
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.1")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → 2 → value", t, func() {
|
||||||
|
doc.Walk(func(key string, val interface{}) error {
|
||||||
|
So(key, ShouldResemble, "the.item.arrays.2")
|
||||||
|
So(val, ShouldResemble, map[string]interface{}{"test": "tre"})
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.2")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → 3 → value", t, func() {
|
||||||
|
err := doc.Walk(func(key string, val interface{}) error {
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.3")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → 0 → value → value", t, func() {
|
||||||
|
err := doc.Walk(func(key string, val interface{}) error {
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.0.test.value")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can walk array → 0 → value → value → value", t, func() {
|
||||||
|
err := doc.Walk(func(key string, val interface{}) error {
|
||||||
|
return nil
|
||||||
|
}, "the.item.arrays.0.test.value.value")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can force error from walk", t, func() {
|
||||||
|
err := doc.Walk(func(key string, val interface{}) error {
|
||||||
|
return errors.New("Testing")
|
||||||
|
}, "the.item.something")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can force error from walk array → *", t, func() {
|
||||||
|
err := doc.Walk(func(key string, val interface{}) error {
|
||||||
|
return errors.New("Testing")
|
||||||
|
}, "the.item.arrays.*")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Can force error from walk array → * → value", t, func() {
|
||||||
|
err := doc.Walk(func(key string, val interface{}) error {
|
||||||
|
return errors.New("Testing")
|
||||||
|
}, "the.item.arrays.*.test")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
Convey("Can copy object", t, func() {
|
Convey("Can copy object", t, func() {
|
||||||
So(doc.Copy(), ShouldResemble, doc.Data())
|
So(doc.Copy(), ShouldResemble, doc.Data())
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue