gabs/gabs.go
2023-06-08 14:58:09 +08:00

890 lines
30 KiB
Go

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