This commit is contained in:
lxinyu 2023-06-08 14:58:09 +08:00
commit 241793b157
3 changed files with 956 additions and 0 deletions

889
gabs.go Normal file
View File

@ -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())
}
//------------------------------------------------------------------------------

12
go.mod Normal file
View File

@ -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

55
go.sum Normal file
View File

@ -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=