diff --git a/util/json/json.go b/util/json/json.go new file mode 100644 index 00000000..0b24a875 --- /dev/null +++ b/util/json/json.go @@ -0,0 +1,414 @@ +// 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 json + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" +) + +// ----------------------------------------------------------------------------------------- + +var ( + // ErrOutOfBounds - Index out of bounds. + ErrOutOfBounds = errors.New("out of bounds") + + // ErrNotObj - The target is not an object type. + ErrNotObj = errors.New("not an object") + + // ErrNotArray - The target is not an array type. + ErrNotArray = errors.New("not an array") + + // ErrNotUnique - The target is not an array type. + ErrNotUnique = errors.New("not a unique array item") + + // ErrPathCollision - Creating a path failed because an element collided with an existing value. + ErrPathCollision = errors.New("encountered value collision whilst building path") + + // ErrInvalidInputObj - The input value was not a map[string]interface{}. + ErrInvalidInputObj = errors.New("invalid input object") + + // ErrInvalidInputText - The input data could not be parsed. + ErrInvalidInputText = errors.New("input text could not be parsed") + + // ErrInvalidPath - The filepath was not valid. + ErrInvalidPath = errors.New("invalid file path") + + // ErrInvalidBuffer - The input buffer contained an invalid JSON string + ErrInvalidBuffer = errors.New("input buffer contained invalid JSON") +) + +// ----------------------------------------------------------------------------------------- + +// Doc - an struct that holds a reference to the core json. +type Doc struct { + data interface{} +} + +type Fmt struct { + doc *Doc + val string +} + +// Data - Return the contained data as an interface{}. +func (d *Doc) Data() interface{} { + return d.data +} + +// Data - Return the contained data as an interface{}. +func (d *Doc) Reset() { + d.data = nil +} + +// ----------------------------------------------------------------------------------------- + +// d.path("this", "is", "a.string", "and.something", "else") + +func (d *Doc) path(path ...string) (paths []string) { + for _, p := range path { + paths = append(paths, strings.Split(p, ".")...) + } + return +} + +// Search - Attempt to find and return an object within the JSON structure by specifying the hierarchy +// of field names to locate the target. If the search encounters an array and has not reached the end +// target then it will iterate each object of the array for the target and return all of the results in +// a JSON array. +func (d *Doc) Search(path ...string) *Doc { + + path = d.path(path...) + + var object interface{} + + object = d.data + for target := 0; target < len(path); target++ { + if mmap, ok := object.(map[string]interface{}); ok { + object = mmap[path[target]] + } else if marray, ok := object.([]interface{}); ok { + tmpArray := []interface{}{} + for _, val := range marray { + tmpGabs := &Doc{val} + res := tmpGabs.Search(path[target:]...).Data() + if res != nil { + tmpArray = append(tmpArray, res) + } + } + if len(tmpArray) == 0 { + return &Doc{nil} + } + return &Doc{tmpArray} + } else { + return &Doc{nil} + } + } + return &Doc{object} +} + +// Exists - Checks whether a path exists. +func (d *Doc) Exists(path ...string) bool { + + path = d.path(path...) + + var object interface{} + + object = d.data + for target := 0; target < len(path); target++ { + if mmap, ok := object.(map[string]interface{}); ok { + object, ok = mmap[path[target]] + if !ok { + return false + } + } else { + return false + } + } + return true +} + +// Index - Attempt to find and return an object with a JSON array by specifying the index of the +// target. +func (d *Doc) Index(index int) *Doc { + if array, ok := d.Data().([]interface{}); ok { + if index >= len(array) { + return &Doc{nil} + } + return &Doc{array[index]} + } + return &Doc{nil} +} + +// Children - Return a slice of all the children of the array. This also works for objects, however, +// the children returned for an object will NOT be in order and you lose the names of the returned +// objects this way. +func (d *Doc) Children() ([]*Doc, error) { + if array, ok := d.Data().([]interface{}); ok { + children := make([]*Doc, len(array)) + for i := 0; i < len(array); i++ { + children[i] = &Doc{array[i]} + } + return children, nil + } + return nil, ErrNotArray +} + +// ChildrenMap - Return a map of all the children of an object. +func (d *Doc) ChildrenMap() (map[string]*Doc, error) { + if mmap, ok := d.Data().(map[string]interface{}); ok { + children := map[string]*Doc{} + for name, obj := range mmap { + children[name] = &Doc{obj} + } + return children, nil + } + return nil, ErrNotObj +} + +func (d *Doc) Fmt(format string, vars ...interface{}) *Fmt { + return &Fmt{ + doc: d, + val: fmt.Sprintf(format, vars...), + } +} + +func (f *Fmt) Set(path ...string) (*Doc, error) { + return f.doc.Set(f.val, path...) +} + +// ----------------------------------------------------------------------------------------- + +func (d *Doc) New(value interface{}, path ...string) (*Doc, error) { + if !d.Exists(path...) { + return d.Set(value, path...) + } + return nil, nil +} + +// Set - Set the value of a field at a JSON path, any parts of the path that do not exist will be +// constructed, and if a collision occurs with a non object type whilst iterating the path an error is +// returned. +func (d *Doc) Set(value interface{}, path ...string) (*Doc, error) { + + path = d.path(path...) + + if len(path) == 0 { + d.data = value + return d, nil + } + var object interface{} + if d.data == nil { + d.data = map[string]interface{}{} + } + object = d.data + for target := 0; target < len(path); target++ { + if mmap, ok := object.(map[string]interface{}); ok { + if target == len(path)-1 { + mmap[path[target]] = value + } else if mmap[path[target]] == nil { + mmap[path[target]] = map[string]interface{}{} + } + object = mmap[path[target]] + } else { + return &Doc{nil}, ErrPathCollision + } + } + return &Doc{object}, nil +} + +// Del - Delete an element at a JSON path, an error is returned if the element does not exist. +func (d *Doc) Del(path ...string) error { + + path = d.path(path...) + + var object interface{} + + if d.data == nil { + return ErrNotObj + } + object = d.data + for target := 0; target < len(path); target++ { + if mmap, ok := object.(map[string]interface{}); ok { + if target == len(path)-1 { + delete(mmap, path[target]) + } else if mmap[path[target]] == nil { + return ErrNotObj + } + object = mmap[path[target]] + } else { + return ErrNotObj + } + } + return nil +} + +// SetIndex - Set a value of an array element based on the index. +func (d *Doc) SetIndex(value interface{}, index int) (*Doc, error) { + if array, ok := d.Data().([]interface{}); ok { + if index >= len(array) { + return &Doc{nil}, ErrOutOfBounds + } + array[index] = value + return &Doc{array[index]}, nil + } + return &Doc{nil}, ErrNotArray +} + +// Object - Create a new JSON object at a path. Returns an error if the path contains a collision with +// a non object type. +func (d *Doc) Object(path ...string) (*Doc, error) { + return d.Set(map[string]interface{}{}, path...) +} + +// Array - Create a new JSON array at a path. Returns an error if the path contains a collision with +// a non object type. +func (d *Doc) Array(path ...string) (*Doc, error) { + return d.Set([]interface{}{}, path...) +} + +// ----------------------------------------------------------------------------------------- + +// ArrayAdd - Append a unique value onto a JSON array. +func (d *Doc) ArrayAdd(value interface{}, path ...string) error { + array, ok := d.Search(path...).Data().([]interface{}) + if !ok { + return ErrNotArray + } + for _, item := range array { + if reflect.DeepEqual(item, value) { + return ErrNotUnique + } + } + array = append(array, value) + _, err := d.Set(array, path...) + return err +} + +// ArrayDel - Append a unique value onto a JSON array. +func (d *Doc) ArrayDel(value interface{}, path ...string) error { + array, ok := d.Search(path...).Data().([]interface{}) + if !ok { + return ErrNotArray + } + for i, item := range array { + if reflect.DeepEqual(item, value) { + array = append(array[:i], array[i+1:]...) + break + } + } + _, err := d.Set(array, path...) + return err +} + +// ArrayAppend - Append a value onto a JSON array. +func (d *Doc) ArrayAppend(value interface{}, path ...string) error { + array, ok := d.Search(path...).Data().([]interface{}) + if !ok { + return ErrNotArray + } + array = append(array, value) + _, err := d.Set(array, path...) + return err +} + +// ArrayRemove - Remove an element from a JSON array. +func (d *Doc) ArrayRemove(index int, path ...string) error { + if index < 0 { + return ErrOutOfBounds + } + array, ok := d.Search(path...).Data().([]interface{}) + if !ok { + return ErrNotArray + } + if index < len(array) { + array = append(array[:index], array[index+1:]...) + } else { + return ErrOutOfBounds + } + _, err := d.Set(array, path...) + return err +} + +// ArrayElement - Access an element from a JSON array. +func (d *Doc) ArrayElement(index int, path ...string) (*Doc, error) { + if index < 0 { + return &Doc{nil}, ErrOutOfBounds + } + array, ok := d.Search(path...).Data().([]interface{}) + if !ok { + return &Doc{nil}, ErrNotArray + } + if index < len(array) { + return &Doc{array[index]}, nil + } + return &Doc{nil}, ErrOutOfBounds +} + +// ArrayCount - Count the number of elements in a JSON array. +func (d *Doc) ArrayCount(path ...string) (int, error) { + if array, ok := d.Search(path...).Data().([]interface{}); ok { + return len(array), nil + } + return 0, ErrNotArray +} + +// ----------------------------------------------------------------------------------------- + +// Bytes - Converts the contained object back to a JSON []byte blob. +func (d *Doc) Bytes() []byte { + if d.data != nil { + if bytes, err := json.Marshal(d.data); err == nil { + return bytes + } + } + return []byte("{}") +} + +// String - Converts the contained object back to a JSON formatted string. +func (d *Doc) String() string { + if d.data != nil { + if bytes, err := json.Marshal(d.data); err == nil { + return string(bytes) + } + } + return "{}" +} + +// New - Create a new gabs JSON object. +func New() *Doc { + return &Doc{map[string]interface{}{}} +} + +// Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object. +func Consume(root interface{}) (*Doc, error) { + return &Doc{root}, nil +} + +func Setup() (*Doc, error) { + return &Doc{map[string]interface{}{}}, nil +} + +// ParseJSON - Convert a string into a representation of the parsed JSON. +func Parse(sample []byte) (*Doc, error) { + var doc Doc + + if err := json.Unmarshal(sample, &doc.data); err != nil { + return nil, err + } + + return &doc, nil +}