// Copyright © 2016 Abcum Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package data import ( "fmt" "reflect" "strconv" "strings" "encoding/json" "github.com/abcum/surreal/util/pack" ) const ( one int8 = iota many ) const ( choose int8 = iota remove ) // Doc holds a reference to the core data object, or a selected path. type Doc struct { data interface{} } // Fetcher is used when fetching values. type Fetcher func(key string, val interface{}, path []string) interface{} // Iterator is used when iterating over items. type Iterator func(key string, val interface{}) error // Walker is used when walking over items. type Walker func(key string, val interface{}, exi bool) error // New creates a new data object. func New() *Doc { return &Doc{data: map[string]interface{}{}} } // Consume converts a GO interface into a data object. func Consume(input interface{}) *Doc { return &Doc{data: input} } // Data returns the internal data object as an interface. func (d *Doc) Data() interface{} { return d.data } // Copy returns a duplicated copy of the internal data object. func (d *Doc) Copy() *Doc { return new(Doc).Decode(d.Encode()) } // Encode encodes the data object to a byte slice. func (d *Doc) Encode() (dst []byte) { dst = pack.Encode(d.data) return } // Decode decodes the byte slice into a data object. func (d *Doc) Decode(src []byte) *Doc { pack.Decode(src, &d.data) return d } func (d *Doc) MarshalJSON() ([]byte, error) { return json.Marshal(d.Data()) } func (d *Doc) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &d.data) } // -------------------------------------------------------------------------------- func (d *Doc) path(path ...string) (paths []string) { l := len(path) for _, p := range path { l += strings.Count(p, ".") l += strings.Count(p, "[") } paths = make([]string, 0, l) for _, p := range path { for j, i, o := 0, 0, false; i < len(p); i++ { switch { case i == len(p)-1: if len(p[j:]) > 0 { paths = append(paths, p[j:]) } case p[i] == '.': if len(p[j:i]) > 0 { paths = append(paths, p[j:i]) } j, i = i+1, i+0 case p[i] == '[': if len(p[j:i]) > 0 { paths = append(paths, p[j:i]) } j, i, o = i, i+1, true case p[i] == ']' && o: if len(p[j:i+1]) > 0 { paths = append(paths, p[j:i+1]) } j, i, o = i+1, i+0, false } } } return } func min(a, b int) int { if a < b { return a } return b } func max(a, b int) int { if a > b { return a } return b } func trim(s string) string { if s[0] == '[' && s[len(s)-1] == ']' { return s[1 : len(s)-1] } return s } func (d *Doc) what(p string, a []interface{}, t int8) (o []interface{}, i []int, r int8) { p = trim(p) i = []int{} o = []interface{}{} // If there are no items in the // original array, then return // an empty array immediately. if len(a) == 0 { if strings.ContainsAny(p, ":*") { return o, i, many } else { return o, i, one } } // If the array index is a star // or a colon only, then return // the full array immediately if p == "*" || p == ":" { switch t { case choose: for k := range a { i = append(i, k) } return a, i, many case remove: for k := range a { i = append(i, k) } return o, i, many } } // Split the specified array index // by colons, so that we can get // the specified array items. c := strings.Count(p, ":") if c == 0 { switch p { case "0", "first": if t == choose { i = append(i, 0) o = append(o, a[0]) } else { for k := range a[1:] { i = append(i, k) } o = append(o, a[1:]...) } case "$", "last": if t == choose { i = append(i, len(a)-1) o = append(o, a[len(a)-1]) } else { for k := range a[:len(a)-1] { i = append(i, k) } o = append(o, a[:len(a)-1]...) } default: if z, e := strconv.Atoi(p); e == nil { if len(a) > z { switch t { case choose: i = append(i, z) o = append(o, a[z]) case remove: for k := range append(a[:z], a[z+1:]...) { i = append(i, k) } o = append(o, append(a[:z], a[z+1:]...)...) } } else { switch t { case remove: for k := range a { i = append(i, k) } o = append(o, a[:]...) } } } } return o, i, one } if c == 1 { var e error var s, f int b := []int{0, len(a)} x := strings.Split(p, ":") for k := range x { switch x[k] { case "": case "0", "first": b[k] = 0 case "$", "last": b[k] = len(a) default: if b[k], e = strconv.Atoi(x[k]); e != nil { return nil, nil, many } } } s = b[0] s = max(s, 0) s = min(s, len(a)) f = b[1] f = max(f, 0) f = min(f, len(a)) if t == choose { for k, v := range a[s:f] { i = append(i, k) o = append(o, v) } } else { for k, v := range append(a[:s], a[f+1:]...) { i = append(i, k) o = append(o, v) } } return o, i, many } return nil, nil, many } // -------------------------------------------------------------------------------- // Reset empties and resets the data at the specified path. func (d *Doc) Reset(path ...string) (*Doc, error) { return d.Set(map[string]interface{}{}, path...) } // Valid checks whether the value at the specified path is nil. func (d *Doc) Valid(path ...string) bool { if !d.Exists(path...) { return false } return d.Get(path...).Data() != nil } // Array sets the specified path to an array. func (d *Doc) Array(path ...string) (*Doc, error) { return d.Set([]interface{}{}, path...) } // Object sets the specified path to an object. func (d *Doc) Object(path ...string) (*Doc, error) { return d.Set(map[string]interface{}{}, path...) } // -------------------------------------------------------------------------------- // New sets the value at the specified path if it does not exist. func (d *Doc) New(value interface{}, path ...string) (*Doc, error) { if !d.Exists(path...) { return d.Set(value, path...) } return d.Get(path...), nil } // Iff sets the value at the specified path if it is not nil, or deletes it. func (d *Doc) Iff(value interface{}, path ...string) (*Doc, error) { if value != nil { return d.Set(value, path...) } return &Doc{data: nil}, d.Del(path...) } // Keys retrieves the object keys at the specified path. func (d *Doc) Keys(path ...string) *Doc { path = d.path(path...) out := []interface{}{} if m, ok := d.Get(path...).Data().(map[string]interface{}); ok { for k := range m { out = append(out, k) } } return &Doc{data: out} } // Vals retrieves the object values at the specified path. func (d *Doc) Vals(path ...string) *Doc { path = d.path(path...) out := []interface{}{} if m, ok := d.Get(path...).Data().(map[string]interface{}); ok { for _, v := range m { out = append(out, v) } } return &Doc{data: out} } // -------------------------------------------------------------------------------- // Exists checks whether the specified path exists. func (d *Doc) Exists(path ...string) bool { path = d.path(path...) // If the value found at the current // path part is undefined, then just // return false immediately if d.data == nil { return false } // 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 { p = trim(p) // 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 false } 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 { c, _, r := d.what(p, a, choose) if len(c) == 0 { return false } if r == one { return Consume(c[0]).Exists(path[k+1:]...) } if r == many { for _, v := range c { if !Consume(v).Exists(path[k+1:]...) { return false } } return true } } return false } return true } // -------------------------------------------------------------------------------- // Get gets the value or values at a specified path. func (d *Doc) Get(path ...string) *Doc { return d.Fetch(nil, path...) } // Fetch gets the value or values at a specified path, allowing for a custom fetch method. func (d *Doc) Fetch(call Fetcher, path ...string) *Doc { path = d.path(path...) // If the value found at the current // path part is undefined, then just // return false immediately if d.data == nil { return &Doc{data: nil} } // 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 { p = trim(p) // 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 { switch p { default: if call != nil { object = call(p, m[p], path[k+1:]) } else { object = m[p] } case "*": object = m } 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 { c, _, r := d.what(p, a, choose) if len(c) == 0 { switch r { case one: return &Doc{data: nil} case many: return &Doc{data: []interface{}{}} } } if r == one { if call != nil { c[0] = call(p, c[0], path[k+1:]) } return Consume(c[0]).Fetch(call, path[k+1:]...) } if r == many { out := []interface{}{} for _, v := range c { if call != nil { v = call(p, v, path[k+1:]) } res := Consume(v).Fetch(call, path[k+1:]...) out = append(out, res.data) } return &Doc{data: out} } } return &Doc{data: nil} } return &Doc{data: object} } // -------------------------------------------------------------------------------- // Set sets the value or values at a specified path. func (d *Doc) Set(value interface{}, path ...string) (*Doc, error) { path = d.path(path...) if len(path) == 0 { d.data = value return d, 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 { p = trim(p) // 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 k == len(path)-1 { m[p] = value } else if m[p] == nil { m[p] = map[string]interface{}{} } object = m[p] 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 { c, i, r := d.what(p, a, choose) if len(c) == 0 { switch r { case one: return &Doc{data: nil}, nil case many: return &Doc{data: []interface{}{}}, nil } } if r == one { if k == len(path)-1 { a[i[0]] = value object = a[i[0]] continue } else { return Consume(a[i[0]]).Set(value, path[k+1:]...) } } if r == many { out := []interface{}{} for j, v := range c { if k == len(path)-1 { a[i[j]] = value out = append(out, value) } else { res, _ := Consume(v).Set(value, path[k+1:]...) if res.data != nil { out = append(out, res.data) } } } return &Doc{data: out}, nil } } } return &Doc{data: object}, nil } // -------------------------------------------------------------------------------- // Del deletes the value or values at a specified path. func (d *Doc) Del(path ...string) error { path = d.path(path...) // If the value found at the current // path part is undefined, then return // a not an object error if d.data == nil { return fmt.Errorf("Item is not an object") } // 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 { p = trim(p) // 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 k == len(path)-1 { delete(m, p) } else if m[p] == nil { return fmt.Errorf("Item at path %s is not an object", path) } object = m[p] 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 r int8 var c []interface{} if k == len(path)-1 { c, _, r = d.what(p, a, remove) } else { c, _, r = d.what(p, a, choose) } if r == one { if k == len(path)-1 { d.Set(c, path[:len(path)-1]...) continue } else { if len(c) != 0 { return Consume(c[0]).Del(path[k+1:]...) } } } if r == many { if k == len(path)-1 { d.Set(c, path[:len(path)-1]...) continue } else { for _, v := range c { Consume(v).Del(path[k+1:]...) } break } } } } return nil } // -------------------------------------------------------------------------------- // Append appends an item or an array of items to an array at the specified path. func (d *Doc) Append(value interface{}, path ...string) (*Doc, error) { a, ok := d.Get(path...).Data().([]interface{}) if !ok { return &Doc{data: nil}, fmt.Errorf("Not an array") } if values, ok := value.([]interface{}); ok { for _, value := range values { a = append(a, value) } } else { a = append(a, value) } return d.Set(a, path...) } // ArrayAdd appends an item or an array of items to an array at the specified path. func (d *Doc) ArrayAdd(value interface{}, path ...string) (*Doc, error) { a, ok := d.Get(path...).Data().([]interface{}) if !ok { return &Doc{data: nil}, fmt.Errorf("Not an array") } if values, ok := value.([]interface{}); ok { outer: for _, value := range values { for _, v := range a { if reflect.DeepEqual(v, value) { continue outer } } a = append(a, value) } } else { for _, v := range a { if reflect.DeepEqual(v, value) { return Consume(a), nil } } a = append(a, value) } return d.Set(a, path...) } // ArrayDel deletes an item or an array of items from an array at the specified path. func (d *Doc) ArrayDel(value interface{}, path ...string) (*Doc, error) { a, ok := d.Get(path...).Data().([]interface{}) if !ok { return &Doc{data: nil}, fmt.Errorf("Not an array") } if values, ok := value.([]interface{}); ok { for _, value := range values { for i := len(a) - 1; i >= 0; i-- { v := a[i] if reflect.DeepEqual(v, value) { copy(a[i:], a[i+1:]) a[len(a)-1] = nil a = a[:len(a)-1] } } } } else { for i := len(a) - 1; i >= 0; i-- { v := a[i] if reflect.DeepEqual(v, value) { copy(a[i:], a[i+1:]) a[len(a)-1] = nil a = a[:len(a)-1] } } } return d.Set(a, path...) } // -------------------------------------------------------------------------------- // Contains checks whether the value exists within the array at the specified path. func (d *Doc) Contains(value interface{}, path ...string) bool { a, ok := d.Get(path...).Data().([]interface{}) if !ok { return false } for _, v := range a { if reflect.DeepEqual(v, value) { return true } } return false } // -------------------------------------------------------------------------------- // Inc increments an item, or appends an item to an array at the specified path. func (d *Doc) Inc(value interface{}, path ...string) (*Doc, error) { switch cur := d.Get(path...).Data().(type) { case nil: switch inc := value.(type) { case int64: return d.Set(0+inc, path...) case float64: return d.Set(0+inc, path...) default: switch value.(type) { case []interface{}: return d.Set(value, path...) default: return d.Set([]interface{}{value}, path...) } } case int64: switch inc := value.(type) { case int64: return d.Set(cur+inc, path...) case float64: return d.Set(float64(cur)+inc, path...) } case float64: switch inc := value.(type) { case int64: return d.Set(cur+float64(inc), path...) case float64: return d.Set(cur+inc, path...) } case []interface{}: return d.ArrayAdd(value, path...) } return &Doc{data: nil}, fmt.Errorf("Not possible to increment.") } // Dec decrements an item, or removes an item from an array at the specified path. func (d *Doc) Dec(value interface{}, path ...string) (*Doc, error) { switch cur := d.Get(path...).Data().(type) { case nil: switch inc := value.(type) { case int64: return d.Set(0-inc, path...) case float64: return d.Set(0-inc, path...) } case int64: switch inc := value.(type) { case int64: return d.Set(cur-inc, path...) case float64: return d.Set(float64(cur)-inc, path...) } case float64: switch inc := value.(type) { case int64: return d.Set(cur-float64(inc), path...) case float64: return d.Set(cur-inc, path...) } case []interface{}: return d.ArrayDel(value, path...) } return &Doc{data: nil}, fmt.Errorf("Not possible to decrement.") } // -------------------------------------------------------------------------------- func (d *Doc) Same(n *Doc) bool { switch a := d.data.(type) { case []interface{}: switch b := n.data.(type) { case map[string]interface{}: return false case []interface{}: if len(a) != len(b) { return false } break } case map[string]interface{}: switch b := n.data.(type) { case []interface{}: return false case map[string]interface{}: if len(a) != len(b) { return false } break } } return reflect.DeepEqual(d, n) } func (d *Doc) Diff(n *Doc) map[string]interface{} { var initial = make(map[string]interface{}) var current = make(map[string]interface{}) var changes = make(map[string]interface{}) d.Each(func(key string, val interface{}) error { initial[key] = val return nil }) n.Each(func(key string, val interface{}) error { current[key] = val return nil }) for k, v := range current { if o, ok := initial[k]; ok { if reflect.DeepEqual(o, v) { continue } } changes[k] = v } for k := range initial { if _, ok := current[k]; !ok { changes[k] = nil } } return changes } // -------------------------------------------------------------------------------- func (d *Doc) join(parts ...[]string) string { var path []string for _, part := range parts { path = append(path, part...) } return strings.Join(path, ".") } // -------------------------------------------------------------------------------- // Each loops through the values in the data doc. func (d *Doc) Each(exec Iterator) error { return d.each(exec, nil) } func (d *Doc) each(exec Iterator, prev []string) error { // Define the temporary object so // that we can loop over and traverse // down the path parts of the data object := d.data // 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 { exec(d.join(prev), make(map[string]interface{})) for k, v := range m { var keep []string keep = append(keep, prev...) keep = append(keep, k) Consume(v).each(exec, keep) } return nil } // 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 { exec(d.join(prev), make([]interface{}, len(a))) for i, v := range a { var keep []string keep = append(keep, prev...) keep = append(keep, fmt.Sprintf("[%d]", i)) Consume(v).each(exec, keep) } return nil } return exec(d.join(prev), object) } // -------------------------------------------------------------------------------- // Walk walks the value or values at a specified path. func (d *Doc) Walk(exec Walker, path ...string) error { path = d.path(path...) return d.walk(exec, nil, path...) } func (d *Doc) walk(exec Walker, prev []string, path ...string) error { if len(path) == 0 { 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 { p = trim(p) // 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, false) } 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 { c, i, r := d.what(p, a, choose) if r == one && len(c) == 0 { return nil } if r == one { if k == len(path)-1 { var keep []string keep = append(keep, prev...) keep = append(keep, path[:k]...) keep = append(keep, fmt.Sprintf("[%d]", i[0])) return exec(d.join(keep), c[0], true) } else { var keep []string keep = append(keep, prev...) keep = append(keep, path[:k]...) keep = append(keep, fmt.Sprintf("[%d]", i[0])) return Consume(c[0]).walk(exec, keep, path[k+1:]...) } } if r == many { for j, v := range c { if k == len(path)-1 { var keep []string keep = append(keep, prev...) keep = append(keep, path[:k]...) keep = append(keep, fmt.Sprintf("[%d]", i[j])) if err := exec(d.join(keep), v, true); err != nil { return err } } else { var keep []string keep = append(keep, prev...) keep = append(keep, path[:k]...) keep = append(keep, fmt.Sprintf("[%d]", i[j])) if err := Consume(v).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 nil } return exec(d.join(prev, path), object, true) }