From 241793b157104a513b1735307ead81a98bf125d5 Mon Sep 17 00:00:00 2001 From: lxinyu Date: Thu, 8 Jun 2023 14:58:09 +0800 Subject: [PATCH] gabs --- gabs.go | 889 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 12 + go.sum | 55 ++++ 3 files changed, 956 insertions(+) create mode 100644 gabs.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/gabs.go b/gabs.go new file mode 100644 index 0000000..b0da5a3 --- /dev/null +++ b/gabs.go @@ -0,0 +1,889 @@ +// Copyright (c) 2019 Ashley Jeffs +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package gabs implements a wrapper around creating and parsing unknown or +// dynamic map structures resulting from JSON parsing. +package gabs + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + + //E "github.com/duolabmeng6/goefun/core" + "io" + "io/ioutil" + "strconv" + "strings" + + C "gitlab.lxinyu.cn/lxinyu/go/c" +) + +//------------------------------------------------------------------------------ + +var ( + // ErrOutOfBounds indicates an index was out of bounds. + ErrOutOfBounds = errors.New("out of bounds") + + // ErrNotObjOrArray is returned when a target is not an object or array type + // but needs to be for the intended operation. + ErrNotObjOrArray = errors.New("not an object or array") + + // ErrNotObj is returned when a target is not an object but needs to be for + // the intended operation. + ErrNotObj = errors.New("not an object") + + // ErrInvalidQuery is returned when a seach query was not valid. + ErrInvalidQuery = errors.New("invalid search query") + + // ErrNotArray is returned when a target is not an array but needs to be for + // the intended operation. + ErrNotArray = errors.New("not an array") + + // ErrPathCollision is returned when creating a path failed because an + // element collided with an existing value. + ErrPathCollision = errors.New("encountered value collision whilst building path") + + // ErrInvalidInputObj is returned when the input value was not a + // map[string]interface{}. + ErrInvalidInputObj = errors.New("invalid input object") + + // ErrInvalidInputText is returned when the input data could not be parsed. + ErrInvalidInputText = errors.New("input text could not be parsed") + + // ErrNotFound is returned when a query leaf is not found. + ErrNotFound = errors.New("field not found") + + // ErrInvalidPath is returned when the filepath was not valid. + ErrInvalidPath = errors.New("invalid file path") + + // ErrInvalidBuffer is returned when the input buffer contained an invalid + // JSON string. + ErrInvalidBuffer = errors.New("input buffer contained invalid JSON") +) + +//------------------------------------------------------------------------------ + +// JSONPointerToSlice parses a JSON pointer path +// (https://tools.ietf.org/html/rfc6901) and returns the path segments as a +// slice. +// +// Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in +// gabs paths, '~' needs to be encoded as '~0' and '/' needs to be encoded as +// '~1' when these characters appear in a reference key. +func JSONPointerToSlice(path string) ([]string, error) { + if len(path) < 1 { + return nil, errors.New("failed to resolve JSON pointer: path must not be empty") + } + if path[0] != '/' { + return nil, errors.New("failed to resolve JSON pointer: path must begin with '/'") + } + hierarchy := strings.Split(path, "/")[1:] + for i, v := range hierarchy { + v = strings.Replace(v, "~1", "/", -1) + v = strings.Replace(v, "~0", "~", -1) + hierarchy[i] = v + } + return hierarchy, nil +} + +// DotPathToSlice returns a slice of path segments parsed out of a dot path. +// +// Because the characters '~' (%x7E) and '.' (%x2E) have special meanings in +// gabs paths, '~' needs to be encoded as '~0' and '.' needs to be encoded as +// '~1' when these characters appear in a reference key. +func DotPathToSlice(path string) []string { + hierarchy := strings.Split(path, ".") + for i, v := range hierarchy { + v = strings.Replace(v, "~1", ".", -1) + v = strings.Replace(v, "~0", "~", -1) + hierarchy[i] = v + } + return hierarchy +} + +//------------------------------------------------------------------------------ + +// Container references a specific element within a wrapped structure. +type Container struct { + object interface{} +} + +// Data returns the underlying value of the target element in the wrapped +// structure. +func (g *Container) Data() interface{} { + if g == nil { + return nil + } + return g.object +} +func (g *Container) ToString() string { + if g == nil { + return "" + } + //return g.object.(string) + return C.C到文本(g.object) +} +func (g *Container) ToInt() int { + if g == nil { + return -1 + } + int, _ := strconv.Atoi(g.object.(string)) + return int +} + +func (g *Container) GetString(key string) string { + if g.ExistsP(key) { + value := g.Path(key).Data() + return C.C到文本(value) + } + return "" +} +func (g *Container) GetArrayAllData(s string) []*Container { + objects := g.Path(s).Children() + return objects +} +func (g *Container) GetArrayCount(s string) int { + int, _ := g.ArrayCountP(s) + return int +} + +func (g *Container) E取文本(key string) string { + return g.GetString(key) +} + +func (g *Container) GetInt(key string) int64 { + if g.ExistsP(key) { + value := g.Path(key).Data().(int64) + return value + } + return 0 +} +func (g *Container) E取值(key string) int64 { + return g.GetInt(key) +} + +func (g *Container) GetBool(key string) bool { + if g.ExistsP(key) { + value := g.Path(key).Data().(bool) + return value + } + return false +} +func (g *Container) E取逻辑值(key string) bool { + return g.GetBool(key) +} + +//------------------------------------------------------------------------------ + +func (g *Container) searchStrict(allowWildcard bool, hierarchy ...string) (*Container, error) { + object := g.Data() + for target := 0; target < len(hierarchy); target++ { + pathSeg := hierarchy[target] + if mmap, ok := object.(map[string]interface{}); ok { + object, ok = mmap[pathSeg] + if !ok { + return nil, fmt.Errorf("failed to resolve path segment '%v': key '%v' was not found", target, pathSeg) + } + } else if marray, ok := object.([]interface{}); ok { + if allowWildcard && pathSeg == "*" { + tmpArray := []interface{}{} + for _, val := range marray { + if (target + 1) >= len(hierarchy) { + tmpArray = append(tmpArray, val) + } else if res := Wrap(val).Search(hierarchy[target+1:]...); res != nil { + tmpArray = append(tmpArray, res.Data()) + } + } + if len(tmpArray) == 0 { + return nil, nil + } + return &Container{tmpArray}, nil + } + index, err := strconv.Atoi(pathSeg) + if err != nil { + return nil, fmt.Errorf("failed to resolve path segment '%v': found array but segment value '%v' could not be parsed into array index: %v", target, pathSeg, err) + } + if len(marray) <= index { + return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' exceeded target array size of '%v'", target, pathSeg, len(marray)) + } + object = marray[index] + } else { + return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg) + } + } + return &Container{object}, nil +} + +// Search attempts to find and return an object within the wrapped structure by +// following a provided hierarchy of field names to locate the target. +// +// If the search encounters an array then the next hierarchy field name must be +// either a an integer which is interpreted as the index of the target, or the +// character '*', in which case all elements are searched with the remaining +// search hierarchy and the results returned within an array. +func (g *Container) Search(hierarchy ...string) *Container { + c, _ := g.searchStrict(true, hierarchy...) + return c +} + +// Path searches the wrapped structure following a path in dot notation, +// segments of this path are searched according to the same rules as Search. +// +// Because the characters '~' (%x7E) and '.' (%x2E) have special meanings in +// gabs paths, '~' needs to be encoded as '~0' and '.' needs to be encoded as +// '~1' when these characters appear in a reference key. +func (g *Container) Path(path string) *Container { + return g.Search(DotPathToSlice(path)...) +} + +// JSONPointer parses a JSON pointer path (https://tools.ietf.org/html/rfc6901) +// and either returns a *gabs.Container containing the result or an error if the +// referenced item could not be found. +// +// Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in +// gabs paths, '~' needs to be encoded as '~0' and '/' needs to be encoded as +// '~1' when these characters appear in a reference key. +func (g *Container) JSONPointer(path string) (*Container, error) { + hierarchy, err := JSONPointerToSlice(path) + if err != nil { + return nil, err + } + return g.searchStrict(false, hierarchy...) +} + +// S is a shorthand alias for Search. +func (g *Container) S(hierarchy ...string) *Container { + return g.Search(hierarchy...) +} + +// Exists checks whether a field exists within the hierarchy. +func (g *Container) Exists(hierarchy ...string) bool { + return g.Search(hierarchy...) != nil +} + +// ExistsP checks whether a dot notation path exists. +func (g *Container) ExistsP(path string) bool { + return g.Exists(DotPathToSlice(path)...) +} + +// Index attempts to find and return an element within a JSON array by an index. +func (g *Container) Index(index int) *Container { + if array, ok := g.Data().([]interface{}); ok { + if index >= len(array) { + return nil + } + return &Container{array[index]} + } + return nil +} + +// Children returns a slice of all children of an array element. This also works +// for objects, however, the children returned for an object will be in a random +// order and you lose the names of the returned objects this way. If the +// underlying container value isn't an array or map nil is returned. +func (g *Container) Children() []*Container { + if array, ok := g.Data().([]interface{}); ok { + children := make([]*Container, len(array)) + for i := 0; i < len(array); i++ { + children[i] = &Container{array[i]} + } + return children + } + if mmap, ok := g.Data().(map[string]interface{}); ok { + children := []*Container{} + for _, obj := range mmap { + children = append(children, &Container{obj}) + } + return children + } + return nil +} + +// ChildrenMap returns a map of all the children of an object element. IF the +// underlying value isn't a object then an empty map is returned. +func (g *Container) ChildrenMap() map[string]*Container { + if mmap, ok := g.Data().(map[string]interface{}); ok { + children := make(map[string]*Container, len(mmap)) + for name, obj := range mmap { + children[name] = &Container{obj} + } + return children + } + return map[string]*Container{} +} + +//------------------------------------------------------------------------------ + +// Set attempts to set the value of a field located by a hierarchy of field +// names. If the search encounters an array then the next hierarchy field name +// is interpreted as an integer index of an existing element, or the character +// '-', which indicates a new element appended to the end of the array. +// +// Any parts of the hierarchy that do not exist will be constructed as objects. +// This includes parts that could be interpreted as array indexes. +// +// Returns a container of the new value or an error. +func (g *Container) Set(value interface{}, hierarchy ...string) (*Container, error) { + if g == nil { + return nil, errors.New("failed to resolve path, container is nil") + } + if len(hierarchy) == 0 { + g.object = value + return g, nil + } + if g.object == nil { + g.object = map[string]interface{}{} + } + object := g.object + + for target := 0; target < len(hierarchy); target++ { + pathSeg := hierarchy[target] + if mmap, ok := object.(map[string]interface{}); ok { + if target == len(hierarchy)-1 { + object = value + mmap[pathSeg] = object + } else if object = mmap[pathSeg]; object == nil { + mmap[pathSeg] = map[string]interface{}{} + object = mmap[pathSeg] + } + } else if marray, ok := object.([]interface{}); ok { + if pathSeg == "-" { + if target < 1 { + return nil, errors.New("unable to append new array index at root of path") + } + if target == len(hierarchy)-1 { + object = value + } else { + object = map[string]interface{}{} + } + marray = append(marray, object) + if _, err := g.Set(marray, hierarchy[:target]...); err != nil { + return nil, err + } + } else { + index, err := strconv.Atoi(pathSeg) + if err != nil { + return nil, fmt.Errorf("failed to resolve path segment '%v': found array but segment value '%v' could not be parsed into array index: %v", target, pathSeg, err) + } + if len(marray) <= index { + return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' exceeded target array size of '%v'", target, pathSeg, len(marray)) + } + if target == len(hierarchy)-1 { + object = value + marray[index] = object + } else if object = marray[index]; object == nil { + return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg) + } + } + } else { + return nil, ErrPathCollision + } + } + return &Container{object}, nil +} + +// SetP sets the value of a field at a path using dot notation, 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 (g *Container) SetP(value interface{}, path string) (*Container, error) { + return g.Set(value, DotPathToSlice(path)...) +} + +// SetIndex attempts to set a value of an array element based on an index. +func (g *Container) SetIndex(value interface{}, index int) (*Container, error) { + if array, ok := g.Data().([]interface{}); ok { + if index >= len(array) { + return nil, ErrOutOfBounds + } + array[index] = value + return &Container{array[index]}, nil + } + return nil, ErrNotArray +} + +// SetJSONPointer parses a JSON pointer path +// (https://tools.ietf.org/html/rfc6901) and sets the leaf to a value. Returns +// an error if the pointer could not be resolved due to missing fields. +func (g *Container) SetJSONPointer(value interface{}, path string) (*Container, error) { + hierarchy, err := JSONPointerToSlice(path) + if err != nil { + return nil, err + } + return g.Set(value, hierarchy...) +} + +// Object creates a new JSON object at a target path. Returns an error if the +// path contains a collision with a non object type. +func (g *Container) Object(hierarchy ...string) (*Container, error) { + return g.Set(map[string]interface{}{}, hierarchy...) +} + +// ObjectP creates a new JSON object at a target path using dot notation. +// Returns an error if the path contains a collision with a non object type. +func (g *Container) ObjectP(path string) (*Container, error) { + return g.Object(DotPathToSlice(path)...) +} + +// ObjectI creates a new JSON object at an array index. Returns an error if the +// object is not an array or the index is out of bounds. +func (g *Container) ObjectI(index int) (*Container, error) { + return g.SetIndex(map[string]interface{}{}, index) +} + +// Array creates a new JSON array at a path. Returns an error if the path +// contains a collision with a non object type. +func (g *Container) Array(hierarchy ...string) (*Container, error) { + return g.Set([]interface{}{}, hierarchy...) +} + +// ArrayP creates a new JSON array at a path using dot notation. Returns an +// error if the path contains a collision with a non object type. +func (g *Container) ArrayP(path string) (*Container, error) { + return g.Array(DotPathToSlice(path)...) +} + +// ArrayI creates a new JSON array within an array at an index. Returns an error +// if the element is not an array or the index is out of bounds. +func (g *Container) ArrayI(index int) (*Container, error) { + return g.SetIndex([]interface{}{}, index) +} + +// ArrayOfSize creates a new JSON array of a particular size at a path. Returns +// an error if the path contains a collision with a non object type. +func (g *Container) ArrayOfSize(size int, hierarchy ...string) (*Container, error) { + a := make([]interface{}, size) + return g.Set(a, hierarchy...) +} + +// ArrayOfSizeP creates a new JSON array of a particular size at a path using +// dot notation. Returns an error if the path contains a collision with a non +// object type. +func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) { + return g.ArrayOfSize(size, DotPathToSlice(path)...) +} + +// ArrayOfSizeI create a new JSON array of a particular size within an array at +// an index. Returns an error if the element is not an array or the index is out +// of bounds. +func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) { + a := make([]interface{}, size) + return g.SetIndex(a, index) +} + +// Delete an element at a path, an error is returned if the element does not +// exist or is not an object. In order to remove an array element please use +// ArrayRemove. +func (g *Container) Delete(hierarchy ...string) error { + if g == nil || g.object == nil { + return ErrNotObj + } + if len(hierarchy) == 0 { + return ErrInvalidQuery + } + + object := g.object + target := hierarchy[len(hierarchy)-1] + if len(hierarchy) > 1 { + object = g.Search(hierarchy[:len(hierarchy)-1]...).Data() + } + + if obj, ok := object.(map[string]interface{}); ok { + if _, ok = obj[target]; !ok { + return ErrNotFound + } + delete(obj, target) + return nil + } + if array, ok := object.([]interface{}); ok { + if len(hierarchy) < 2 { + return errors.New("unable to delete array index at root of path") + } + index, err := strconv.Atoi(target) + if err != nil { + return fmt.Errorf("failed to parse array index '%v': %v", target, err) + } + if index >= len(array) { + return ErrOutOfBounds + } + array = append(array[:index], array[index+1:]...) + g.Set(array, hierarchy[:len(hierarchy)-1]...) + return nil + } + return ErrNotObjOrArray +} + +// DeleteP deletes an element at a path using dot notation, an error is returned +// if the element does not exist. +func (g *Container) DeleteP(path string) error { + return g.Delete(DotPathToSlice(path)...) +} + +// MergeFn merges two objects using a provided function to resolve collisions. +// +// The collision function receives two interface{} arguments, destination (the +// original object) and source (the object being merged into the destination). +// Which ever value is returned becomes the new value in the destination object +// at the location of the collision. +func (g *Container) MergeFn(source *Container, collisionFn func(destination, source interface{}) interface{}) error { + var recursiveFnc func(map[string]interface{}, []string) error + recursiveFnc = func(mmap map[string]interface{}, path []string) error { + for key, value := range mmap { + newPath := append(path, key) + if g.Exists(newPath...) { + existingData := g.Search(newPath...).Data() + switch t := value.(type) { + case map[string]interface{}: + switch existingVal := existingData.(type) { + case map[string]interface{}: + if err := recursiveFnc(t, newPath); err != nil { + return err + } + default: + if _, err := g.Set(collisionFn(existingVal, t), newPath...); err != nil { + return err + } + } + default: + if _, err := g.Set(collisionFn(existingData, t), newPath...); err != nil { + return err + } + } + } else { + // path doesn't exist. So set the value + if _, err := g.Set(value, newPath...); err != nil { + return err + } + } + } + return nil + } + if mmap, ok := source.Data().(map[string]interface{}); ok { + return recursiveFnc(mmap, []string{}) + } + return nil +} + +// Merge a source object into an existing destination object. When a collision +// is found within the merged structures (both a source and destination object +// contain the same non-object keys) the result will be an array containing both +// values, where values that are already arrays will be expanded into the +// resulting array. +// +// It is possible to merge structures will different collision behaviours with +// MergeFn. +func (g *Container) Merge(source *Container) error { + return g.MergeFn(source, func(dest, source interface{}) interface{} { + destArr, destIsArray := dest.([]interface{}) + sourceArr, sourceIsArray := source.([]interface{}) + if destIsArray { + if sourceIsArray { + return append(destArr, sourceArr...) + } + return append(destArr, source) + } + if sourceIsArray { + return append(append([]interface{}{}, dest), sourceArr...) + } + return []interface{}{dest, source} + }) +} + +//------------------------------------------------------------------------------ + +/* +Array modification/search - Keeping these options simple right now, no need for +anything more complicated since you can just cast to []interface{}, modify and +then reassign with Set. +*/ + +// ArrayAppend attempts to append a value onto a JSON array at a path. If the +// target is not a JSON array then it will be converted into one, with its +// original contents set to the first element of the array. +func (g *Container) ArrayAppend(value interface{}, hierarchy ...string) error { + if array, ok := g.Search(hierarchy...).Data().([]interface{}); ok { + array = append(array, value) + _, err := g.Set(array, hierarchy...) + return err + } + + newArray := []interface{}{} + if d := g.Search(hierarchy...).Data(); d != nil { + newArray = append(newArray, d) + } + newArray = append(newArray, value) + + _, err := g.Set(newArray, hierarchy...) + return err +} + +// ArrayAppendP attempts to append a value onto a JSON array at a path using dot +// notation. If the target is not a JSON array then it will be converted into +// one, with its original contents set to the first element of the array. +func (g *Container) ArrayAppendP(value interface{}, path string) error { + return g.ArrayAppend(value, DotPathToSlice(path)...) +} + +// ArrayConcat attempts to append a value onto a JSON array at a path. If the +// target is not a JSON array then it will be converted into one, with its +// original contents set to the first element of the array. +// +// ArrayConcat differs from ArrayAppend in that it will expand a value type +// []interface{} during the append operation, resulting in concatenation of each +// element, rather than append as a single element of []interface{}. +func (g *Container) ArrayConcat(value interface{}, hierarchy ...string) error { + var array []interface{} + if d := g.Search(hierarchy...).Data(); d != nil { + if targetArray, ok := d.([]interface{}); !ok { + // If the data exists, and it is not a slice of interface, + // append it as the first element of our new array. + array = append(array, d) + } else { + // If the data exists, and it is a slice of interface, + // assign it to our variable. + array = targetArray + } + } + + switch v := value.(type) { + case []interface{}: + // If we have been given a slice of interface, expand it when appending. + array = append(array, v...) + default: + array = append(array, v) + } + + _, err := g.Set(array, hierarchy...) + + return err +} + +// ArrayConcatP attempts to append a value onto a JSON array at a path using dot +// notation. If the target is not a JSON array then it will be converted into one, +// with its original contents set to the first element of the array. +// +// ArrayConcatP differs from ArrayAppendP in that it will expand a value type +// []interface{} during the append operation, resulting in concatenation of each +// element, rather than append as a single element of []interface{}. +func (g *Container) ArrayConcatP(value interface{}, path string) error { + return g.ArrayConcat(value, DotPathToSlice(path)...) +} + +// ArrayRemove attempts to remove an element identified by an index from a JSON +// array at a path. +func (g *Container) ArrayRemove(index int, hierarchy ...string) error { + if index < 0 { + return ErrOutOfBounds + } + array, ok := g.Search(hierarchy...).Data().([]interface{}) + if !ok { + return ErrNotArray + } + if index < len(array) { + array = append(array[:index], array[index+1:]...) + } else { + return ErrOutOfBounds + } + _, err := g.Set(array, hierarchy...) + return err +} + +// ArrayRemoveP attempts to remove an element identified by an index from a JSON +// array at a path using dot notation. +func (g *Container) ArrayRemoveP(index int, path string) error { + return g.ArrayRemove(index, DotPathToSlice(path)...) +} + +// ArrayElement attempts to access an element by an index from a JSON array at a +// path. +func (g *Container) ArrayElement(index int, hierarchy ...string) (*Container, error) { + if index < 0 { + return nil, ErrOutOfBounds + } + array, ok := g.Search(hierarchy...).Data().([]interface{}) + if !ok { + return nil, ErrNotArray + } + if index < len(array) { + return &Container{array[index]}, nil + } + return nil, ErrOutOfBounds +} + +// ArrayElementP attempts to access an element by an index from a JSON array at +// a path using dot notation. +func (g *Container) ArrayElementP(index int, path string) (*Container, error) { + return g.ArrayElement(index, DotPathToSlice(path)...) +} + +// ArrayCount counts the number of elements in a JSON array at a path. +func (g *Container) ArrayCount(hierarchy ...string) (int, error) { + if array, ok := g.Search(hierarchy...).Data().([]interface{}); ok { + return len(array), nil + } + return 0, ErrNotArray +} + +// ArrayCountP counts the number of elements in a JSON array at a path using dot +// notation. +func (g *Container) ArrayCountP(path string) (int, error) { + return g.ArrayCount(DotPathToSlice(path)...) +} + +//------------------------------------------------------------------------------ + +// Bytes marshals an element to a JSON []byte blob. +func (g *Container) Bytes() []byte { + if bytes, err := json.Marshal(g.Data()); err == nil { + return bytes + } + return []byte("null") +} + +// BytesIndent marshals an element to a JSON []byte blob formatted with a prefix +// and indent string. +func (g *Container) BytesIndent(prefix string, indent string) []byte { + if g.object != nil { + if bytes, err := json.MarshalIndent(g.Data(), prefix, indent); err == nil { + return bytes + } + } + return []byte("null") +} + +// String marshals an element to a JSON formatted string. +func (g *Container) String() string { + return string(g.Bytes()) +} + +// StringIndent marshals an element to a JSON string formatted with a prefix and +// indent string. +func (g *Container) StringIndent(prefix string, indent string) string { + return string(g.BytesIndent(prefix, indent)) +} + +// EncodeOpt is a functional option for the EncodeJSON method. +type EncodeOpt func(e *json.Encoder) + +// EncodeOptHTMLEscape sets the encoder to escape the JSON for html. +func EncodeOptHTMLEscape(doEscape bool) EncodeOpt { + return func(e *json.Encoder) { + e.SetEscapeHTML(doEscape) + } +} + +// EncodeOptIndent sets the encoder to indent the JSON output. +func EncodeOptIndent(prefix string, indent string) EncodeOpt { + return func(e *json.Encoder) { + e.SetIndent(prefix, indent) + } +} + +// EncodeJSON marshals an element to a JSON formatted []byte using a variant +// list of modifier functions for the encoder being used. Functions for +// modifying the output are prefixed with EncodeOpt, e.g. EncodeOptHTMLEscape. +func (g *Container) EncodeJSON(encodeOpts ...EncodeOpt) []byte { + var b bytes.Buffer + encoder := json.NewEncoder(&b) + encoder.SetEscapeHTML(false) // Do not escape by default. + for _, opt := range encodeOpts { + opt(encoder) + } + if err := encoder.Encode(g.object); err != nil { + return []byte("null") + } + result := b.Bytes() + if len(result) > 0 { + result = result[:len(result)-1] + } + return result +} + +// New creates a new gabs JSON object. +func New() *Container { + return &Container{map[string]interface{}{}} +} + +// Wrap an already unmarshalled JSON object (or a new map[string]interface{}) +// into a *Container. +func Wrap(root interface{}) *Container { + return &Container{root} +} + +// ParseJSON unmarshals a JSON byte slice into a *Container. +func ParseJSON(sample []byte) (*Container, error) { + var gabs Container + + if err := json.Unmarshal(sample, &gabs.object); err != nil { + return nil, err + } + + return &gabs, nil +} + +// ParseJSONDecoder applies a json.Decoder to a *Container. +func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) { + var gabs Container + + if err := decoder.Decode(&gabs.object); err != nil { + return nil, err + } + + return &gabs, nil +} + +// ParseJSONFile reads a file and unmarshals the contents into a *Container. +func ParseJSONFile(path string) (*Container, error) { + if len(path) > 0 { + cBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + container, err := ParseJSON(cBytes) + if err != nil { + return nil, err + } + + return container, nil + } + return nil, ErrInvalidPath +} + +// ParseJSONBuffer reads a buffer and unmarshals the contents into a *Container. +func ParseJSONBuffer(buffer io.Reader) (*Container, error) { + var gabs Container + jsonDecoder := json.NewDecoder(buffer) + if err := jsonDecoder.Decode(&gabs.object); err != nil { + return nil, err + } + + return &gabs, nil +} + +// MarshalJSON returns the JSON encoding of this container. This allows +// structs which contain Container instances to be marshaled using +// json.Marshal(). +func (g *Container) MarshalJSON() ([]byte, error) { + return json.Marshal(g.Data()) +} + +//------------------------------------------------------------------------------ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b196cd6 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module gabs + +go 1.19 + +require gitlab.lxinyu.cn/lxinyu/go/c v0.0.0 + +require ( + github.com/gogf/gf v1.16.8 // indirect + golang.org/x/text v0.3.7 // indirect +) + +replace gitlab.lxinyu.cn/lxinyu/go/c => C:\Users\Administrator\go\src\gitlab.lxinyu.cn\lxinyu\go\c diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..46a2666 --- /dev/null +++ b/go.sum @@ -0,0 +1,55 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4= +github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gogf/gf v1.16.8 h1:iVXUB+QPQIYDMMjMdDb6ZINF8xf5bWy54XxAo600zMs= +github.com/gogf/gf v1.16.8/go.mod h1:8Q/kw05nlVRp+4vv7XASBsMe9L1tsVKiGoeP2AHnlkk= +github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc= +github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0= +github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI= +go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= +go.opentelemetry.io/otel/trace v1.0.0 h1:TSBr8GTEtKevYMG/2d21M989r5WJYVimhTHBKVEZuh4= +go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=