New Upstream Release - golang-github-jeffail-gabs
Ready changes
Summary
Merged new upstream version: 2.7.0 (was: 2.6.1).
Resulting package
Built on 2023-05-05T16:01 (took 11m2s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-jeffail-gabs-dev
Lintian Result
Diff
diff --git a/.errcheck.txt b/.errcheck.txt
new file mode 100644
index 0000000..7176915
--- /dev/null
+++ b/.errcheck.txt
@@ -0,0 +1,8 @@
+(*github.com/Jeffail/gabs/v2.Container).Array
+(*github.com/Jeffail/gabs/v2.Container).ArrayAppend
+(*github.com/Jeffail/gabs/v2.Container).ArrayAppend
+(*github.com/Jeffail/gabs/v2.Container).ArrayConcat
+(*github.com/Jeffail/gabs/v2.Container).ArrayConcatP
+(*github.com/Jeffail/gabs/v2.Container).ArrayP
+(*github.com/Jeffail/gabs/v2.Container).Set
+(*github.com/Jeffail/gabs/v2.Container).SetIndex
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..f1f4207
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,35 @@
+name: Test
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: 1.16.x
+
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Tidy
+ run: go mod tidy && git diff-index --quiet HEAD || { >&2 echo "Stale go.{mod,sum} detected. This can be fixed with 'go mod tidy'."; exit 1; }
+
+ - name: Test
+ run: go test -count 100 ./...
+
+ golangci-lint:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Lint
+ uses: golangci/golangci-lint-action@v3
+ with:
+ version: latest
+ args: --timeout 10m
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..4dd3bc7
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,41 @@
+run:
+ timeout: 30s
+
+issues:
+ max-issues-per-linter: 0
+ max-same-issues: 0
+
+linters-settings:
+ errcheck:
+ exclude: .errcheck.txt
+ gocritic:
+ enabled-tags:
+ - diagnostic
+ - experimental
+ - opinionated
+ - performance
+ - style
+
+linters:
+ disable-all: true
+ enable:
+ # Default linters reported by golangci-lint help linters` in v1.39.0
+ - gosimple
+ - staticcheck
+ - unused
+ - errcheck
+ - govet
+ - ineffassign
+ - typecheck
+ # Extra linters:
+ - wastedassign
+ - stylecheck
+ - gofmt
+ - goimports
+ - gocritic
+ - revive
+ - unconvert
+ - durationcheck
+ - depguard
+ - gosec
+ - bodyclose
diff --git a/README.md b/README.md
index 6163e7a..64cf19a 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ import (
```go
jsonParsed, err := gabs.ParseJSON([]byte(`{
- "outter":{
+ "outer":{
"inner":{
"value1":10,
"value2":22
@@ -50,16 +50,16 @@ if err != nil {
var value float64
var ok bool
-value, ok = jsonParsed.Path("outter.inner.value1").Data().(float64)
+value, ok = jsonParsed.Path("outer.inner.value1").Data().(float64)
// value == 10.0, ok == true
-value, ok = jsonParsed.Search("outter", "inner", "value1").Data().(float64)
+value, ok = jsonParsed.Search("outer", "inner", "value1").Data().(float64)
// value == 10.0, ok == true
-value, ok = jsonParsed.Search("outter", "alsoInner", "array1", "1").Data().(float64)
+value, ok = jsonParsed.Search("outer", "alsoInner", "array1", "1").Data().(float64)
// value == 40.0, ok == true
-gObj, err := jsonParsed.JSONPointer("/outter/alsoInner/array1/1")
+gObj, err := jsonParsed.JSONPointer("/outer/alsoInner/array1/1")
if err != nil {
panic(err)
}
@@ -69,7 +69,7 @@ value, ok = gObj.Data().(float64)
value, ok = jsonParsed.Path("does.not.exist").Data().(float64)
// value == 0.0, ok == false
-exists := jsonParsed.Exists("outter", "inner", "value1")
+exists := jsonParsed.Exists("outer", "inner", "value1")
// exists == true
exists = jsonParsed.ExistsP("does.not.exist")
@@ -86,7 +86,7 @@ if err != nil {
// S is shorthand for Search
for key, child := range jsonParsed.S("object").ChildrenMap() {
- fmt.Printf("key: %v, value: %v\n", key, child.Data().(string))
+ fmt.Printf("key: %v, value: %v\n", key, child.Data().(float64))
}
```
@@ -133,9 +133,9 @@ Will print `2`.
jsonObj := gabs.New()
// or gabs.Wrap(jsonObject) to work on an existing map[string]interface{}
-jsonObj.Set(10, "outter", "inner", "value")
-jsonObj.SetP(20, "outter.inner.value2")
-jsonObj.Set(30, "outter", "inner2", "value3")
+jsonObj.Set(10, "outer", "inner", "value")
+jsonObj.SetP(20, "outer.inner.value2")
+jsonObj.Set(30, "outer", "inner2", "value3")
fmt.Println(jsonObj.String())
```
@@ -143,7 +143,7 @@ fmt.Println(jsonObj.String())
Will print:
```
-{"outter":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}}
+{"outer":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}}
```
To pretty-print:
@@ -156,7 +156,7 @@ Will print:
```
{
- "outter": {
+ "outer": {
"inner": {
"value": 10,
"value2": 20
@@ -222,33 +222,33 @@ This is the easiest part:
```go
jsonParsedObj, _ := gabs.ParseJSON([]byte(`{
- "outter":{
+ "outer":{
"values":{
"first":10,
"second":11
}
},
- "outter2":"hello world"
+ "outer2":"hello world"
}`))
jsonOutput := jsonParsedObj.String()
-// Becomes `{"outter":{"values":{"first":10,"second":11}},"outter2":"hello world"}`
+// Becomes `{"outer":{"values":{"first":10,"second":11}},"outer2":"hello world"}`
```
And to serialize a specific segment is as simple as:
```go
jsonParsedObj := gabs.ParseJSON([]byte(`{
- "outter":{
+ "outer":{
"values":{
"first":10,
"second":11
}
},
- "outter2":"hello world"
+ "outer2":"hello world"
}`))
-jsonOutput := jsonParsedObj.Search("outter").String()
+jsonOutput := jsonParsedObj.Search("outer").String()
// Becomes `{"values":{"first":10,"second":11}}`
```
@@ -257,11 +257,11 @@ jsonOutput := jsonParsedObj.Search("outter").String()
You can merge a JSON structure into an existing one, where collisions will be converted into a JSON array.
```go
-jsonParsed1, _ := ParseJSON([]byte(`{"outter":{"value1":"one"}}`))
-jsonParsed2, _ := ParseJSON([]byte(`{"outter":{"inner":{"value3":"three"}},"outter2":{"value2":"two"}}`))
+jsonParsed1, _ := ParseJSON([]byte(`{"outer":{"value1":"one"}}`))
+jsonParsed2, _ := ParseJSON([]byte(`{"outer":{"inner":{"value3":"three"}},"outer2":{"value2":"two"}}`))
jsonParsed1.Merge(jsonParsed2)
-// Becomes `{"outter":{"inner":{"value3":"three"},"value1":"one"},"outter2":{"value2":"two"}}`
+// Becomes `{"outer":{"inner":{"value3":"three"},"value1":"one"},"outer2":{"value2":"two"}}`
```
Arrays are merged:
diff --git a/debian/changelog b/debian/changelog
index 6951843..f9278e0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,11 +1,16 @@
-golang-github-jeffail-gabs (2.5.1-1) UNRELEASED; urgency=medium
+golang-github-jeffail-gabs (2.7.0-1) UNRELEASED; urgency=medium
+ [ Dawid Dziurla ]
* d/control: bump standards
* d/copyright: delete Files-Excluded
* New upstream version 2.5.1
* d/control: add newline at the end
- -- Dawid Dziurla <dawidd0811@gmail.com> Sun, 24 May 2020 15:00:02 +0200
+ [ Debian Janitor ]
+ * New upstream release.
+ * New upstream release.
+
+ -- Dawid Dziurla <dawidd0811@gmail.com> Fri, 05 May 2023 15:51:02 -0000
golang-github-jeffail-gabs (2.3.0-1) unstable; urgency=medium
diff --git a/gabs.go b/gabs.go
index 9249f3d..b846954 100644
--- a/gabs.go
+++ b/gabs.go
@@ -28,7 +28,7 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
+ "os"
"strconv"
"strings"
)
@@ -76,6 +76,16 @@ var (
ErrInvalidBuffer = errors.New("input buffer contained invalid JSON")
)
+var (
+ r1 *strings.Replacer
+ r2 *strings.Replacer
+)
+
+func init() {
+ r1 = strings.NewReplacer("~1", "/", "~0", "~")
+ r2 = strings.NewReplacer("~1", ".", "~0", "~")
+}
+
//------------------------------------------------------------------------------
// JSONPointerToSlice parses a JSON pointer path
@@ -86,32 +96,31 @@ var (
// 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 == "" {
+ return nil, nil
}
if path[0] != '/' {
return nil, errors.New("failed to resolve JSON pointer: path must begin with '/'")
}
+ if path == "/" {
+ return []string{""}, nil
+ }
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
+ hierarchy[i] = r1.Replace(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.
+// Because '.' (%x2E) is the segment separator, it must be encoded as '~1'
+// if it appears in the reference key. Likewise, '~' (%x7E) must be encoded
+// as '~0' since it is the escape character for encoding '.'.
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
+ hierarchy[i] = r2.Replace(v)
}
return hierarchy
}
@@ -138,24 +147,30 @@ func (g *Container) searchStrict(allowWildcard bool, hierarchy ...string) (*Cont
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 {
+ switch typedObj := object.(type) {
+ case map[string]interface{}:
+ var ok bool
+ if object, ok = typedObj[pathSeg]; !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 {
+ case []interface{}:
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())
+ var tmpArray []interface{}
+ if (target + 1) >= len(hierarchy) {
+ tmpArray = typedObj
+ } else {
+ tmpArray = make([]interface{}, 0, len(typedObj))
+ for _, val := range typedObj {
+ 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)
@@ -165,11 +180,11 @@ func (g *Container) searchStrict(allowWildcard bool, hierarchy ...string) (*Cont
if index < 0 {
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' is invalid", target, pathSeg)
}
- 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 len(typedObj) <= 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(typedObj))
}
- object = marray[index]
- } else {
+ object = typedObj[index]
+ default:
return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg)
}
}
@@ -252,7 +267,7 @@ func (g *Container) Children() []*Container {
return children
}
if mmap, ok := g.Data().(map[string]interface{}); ok {
- children := []*Container{}
+ children := make([]*Container, 0, len(mmap))
for _, obj := range mmap {
children = append(children, &Container{obj})
}
@@ -300,15 +315,16 @@ func (g *Container) Set(value interface{}, hierarchy ...string) (*Container, err
for target := 0; target < len(hierarchy); target++ {
pathSeg := hierarchy[target]
- if mmap, ok := object.(map[string]interface{}); ok {
+ switch typedObj := object.(type) {
+ case map[string]interface{}:
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]
+ typedObj[pathSeg] = object
+ } else if object = typedObj[pathSeg]; object == nil {
+ typedObj[pathSeg] = map[string]interface{}{}
+ object = typedObj[pathSeg]
}
- } else if marray, ok := object.([]interface{}); ok {
+ case []interface{}:
if pathSeg == "-" {
if target < 1 {
return nil, errors.New("unable to append new array index at root of path")
@@ -318,8 +334,8 @@ func (g *Container) Set(value interface{}, hierarchy ...string) (*Container, err
} else {
object = map[string]interface{}{}
}
- marray = append(marray, object)
- if _, err := g.Set(marray, hierarchy[:target]...); err != nil {
+ typedObj = append(typedObj, object)
+ if _, err := g.Set(typedObj, hierarchy[:target]...); err != nil {
return nil, err
}
} else {
@@ -330,17 +346,17 @@ func (g *Container) Set(value interface{}, hierarchy ...string) (*Container, err
if index < 0 {
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' is invalid", target, pathSeg)
}
- 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 len(typedObj) <= 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(typedObj))
}
if target == len(hierarchy)-1 {
object = value
- marray[index] = object
- } else if object = marray[index]; object == nil {
+ typedObj[index] = object
+ } else if object = typedObj[index]; object == nil {
return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg)
}
}
- } else {
+ default:
return nil, ErrPathCollision
}
}
@@ -496,7 +512,9 @@ func (g *Container) MergeFn(source *Container, collisionFn func(destination, sou
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)
+ newPath := make([]string, len(path))
+ copy(newPath, path)
+ newPath = append(newPath, key)
if g.Exists(newPath...) {
existingData := g.Search(newPath...).Data()
switch t := value.(type) {
@@ -516,11 +534,9 @@ func (g *Container) MergeFn(source *Container, collisionFn func(destination, sou
return err
}
}
- } else {
+ } else if _, err := g.Set(value, newPath...); err != nil {
// path doesn't exist. So set the value
- if _, err := g.Set(value, newPath...); err != nil {
- return err
- }
+ return err
}
}
return nil
@@ -699,23 +715,29 @@ func (g *Container) ArrayCountP(path string) (int, error) {
//------------------------------------------------------------------------------
-func walkObject(path string, obj map[string]interface{}, flat map[string]interface{}) {
+func walkObject(path string, obj, flat map[string]interface{}, includeEmpty bool) {
+ if includeEmpty && len(obj) == 0 {
+ flat[path] = struct{}{}
+ }
for elePath, v := range obj {
if len(path) > 0 {
elePath = path + "." + elePath
}
switch t := v.(type) {
case map[string]interface{}:
- walkObject(elePath, t, flat)
+ walkObject(elePath, t, flat, includeEmpty)
case []interface{}:
- walkArray(elePath, t, flat)
+ walkArray(elePath, t, flat, includeEmpty)
default:
flat[elePath] = t
}
}
}
-func walkArray(path string, arr []interface{}, flat map[string]interface{}) {
+func walkArray(path string, arr []interface{}, flat map[string]interface{}, includeEmpty bool) {
+ if includeEmpty && len(arr) == 0 {
+ flat[path] = []struct{}{}
+ }
for i, ele := range arr {
elePath := strconv.Itoa(i)
if len(path) > 0 {
@@ -723,9 +745,9 @@ func walkArray(path string, arr []interface{}, flat map[string]interface{}) {
}
switch t := ele.(type) {
case map[string]interface{}:
- walkObject(elePath, t, flat)
+ walkObject(elePath, t, flat, includeEmpty)
case []interface{}:
- walkArray(elePath, t, flat)
+ walkArray(elePath, t, flat, includeEmpty)
default:
flat[elePath] = t
}
@@ -737,16 +759,34 @@ func walkArray(path string, arr []interface{}, flat map[string]interface{}) {
// notation matching the spec for the method Path.
//
// E.g. the structure `{"foo":[{"bar":"1"},{"bar":"2"}]}` would flatten into the
-// object: `{"foo.0.bar":"1","foo.1.bar":"2"}`.
+// object: `{"foo.0.bar":"1","foo.1.bar":"2"}`. `{"foo": [{"bar":[]},{"bar":{}}]}`
+// would flatten into the object `{}`
//
// Returns an error if the target is not a JSON object or array.
func (g *Container) Flatten() (map[string]interface{}, error) {
+ return g.flatten(false)
+}
+
+// FlattenIncludeEmpty a JSON array or object into an object of key/value pairs
+// for each field, just as Flatten, but includes empty arrays and objects, where
+// the key is the full path of the structured field in dot path notation matching
+// the spec for the method Path.
+//
+// E.g. the structure `{"foo": [{"bar":[]},{"bar":{}}]}` would flatten into the
+// object: `{"foo.0.bar":[],"foo.1.bar":{}}`.
+//
+// Returns an error if the target is not a JSON object or array.
+func (g *Container) FlattenIncludeEmpty() (map[string]interface{}, error) {
+ return g.flatten(true)
+}
+
+func (g *Container) flatten(includeEmpty bool) (map[string]interface{}, error) {
flattened := map[string]interface{}{}
switch t := g.Data().(type) {
case map[string]interface{}:
- walkObject("", t, flattened)
+ walkObject("", t, flattened, includeEmpty)
case []interface{}:
- walkArray("", t, flattened)
+ walkArray("", t, flattened, includeEmpty)
default:
return nil, ErrNotObjOrArray
}
@@ -757,18 +797,18 @@ func (g *Container) Flatten() (map[string]interface{}, error) {
// 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
+ if data, err := json.Marshal(g.Data()); err == nil {
+ return data
}
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 {
+func (g *Container) BytesIndent(prefix, indent string) []byte {
if g.object != nil {
- if bytes, err := json.MarshalIndent(g.Data(), prefix, indent); err == nil {
- return bytes
+ if data, err := json.MarshalIndent(g.Data(), prefix, indent); err == nil {
+ return data
}
}
return []byte("null")
@@ -781,7 +821,7 @@ func (g *Container) String() string {
// StringIndent marshals an element to a JSON string formatted with a prefix and
// indent string.
-func (g *Container) StringIndent(prefix string, indent string) string {
+func (g *Container) StringIndent(prefix, indent string) string {
return string(g.BytesIndent(prefix, indent))
}
@@ -796,7 +836,7 @@ func EncodeOptHTMLEscape(doEscape bool) EncodeOpt {
}
// EncodeOptIndent sets the encoder to indent the JSON output.
-func EncodeOptIndent(prefix string, indent string) EncodeOpt {
+func EncodeOptIndent(prefix, indent string) EncodeOpt {
return func(e *json.Encoder) {
e.SetIndent(prefix, indent)
}
@@ -858,7 +898,7 @@ func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) {
// 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)
+ cBytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
diff --git a/gabs_test.go b/gabs_test.go
index 695522e..f8429e5 100644
--- a/gabs_test.go
+++ b/gabs_test.go
@@ -38,7 +38,7 @@ func TestBasic(t *testing.T) {
t.Errorf("Didn't find test2")
}
- if result := val.Bytes(); string(result) != string(sample) {
+ if result := val.Bytes(); !bytes.Equal(result, sample) {
t.Errorf("Wrong []byte conversion: %s != %s", result, sample)
}
}
@@ -124,12 +124,28 @@ func TestJSONPointer(t *testing.T) {
path string
value string
err string
+ input string
}
tests := []testCase{
{
path: "foo",
err: "failed to resolve JSON pointer: path must begin with '/'",
},
+ {
+ path: "",
+ value: `{"whole":{"document":"is this"}}`,
+ input: `{"whole":{"document":"is this"}}`,
+ },
+ {
+ path: "/",
+ value: `{"":{"b":"value b"},"a":"value a"}`,
+ input: `{"":{"a":"value a","":{"b":"value b"}}}`,
+ },
+ {
+ path: "//",
+ value: `{"b":"value b"}`,
+ input: `{"":{"a":"value a","":{"b":"value b"}}}`,
+ },
{
path: "/a/doesnotexist",
err: "failed to resolve path segment '1': key 'doesnotexist' was not found",
@@ -158,10 +174,6 @@ func TestJSONPointer(t *testing.T) {
path: "/what~0.a.~0pain",
value: `"ouch5"`,
},
- {
- path: "/",
- value: `{"can we access":"this?"}`,
- },
{
path: "//can we access",
value: `"this?"`,
@@ -192,13 +204,17 @@ func TestJSONPointer(t *testing.T) {
},
}
- root, err := ParseJSON(bigSample)
- if err != nil {
- t.Fatalf("Failed to parse: %v", err)
- }
-
for _, test := range tests {
t.Run(test.path, func(tt *testing.T) {
+ input := test.input
+ if input == "" {
+ input = string(bigSample)
+ }
+ root, err := ParseJSON([]byte(input))
+ if err != nil {
+ t.Fatalf("Failed to parse: %v", err)
+ }
+
var result *Container
result, err = root.JSONPointer(test.path)
if len(test.err) > 0 {
@@ -209,8 +225,7 @@ func TestJSONPointer(t *testing.T) {
}
return
} else if err != nil {
- tt.Error(err)
- return
+ tt.Fatal(err)
}
if exp, act := test.value, result.String(); exp != act {
tt.Errorf("Wrong result: %v != %v", act, exp)
@@ -692,6 +707,10 @@ func TestExamples(t *testing.T) {
}
}
}`))
+ if err != nil {
+ t.Errorf("Error: %v", err)
+ return
+ }
var value float64
var ok bool
@@ -953,11 +972,11 @@ func TestModify(t *testing.T) {
t.Errorf("Didn't find test.value")
}
- if out := val.String(); `{"test":{"value":45},"test2":20}` != out {
+ if out := val.String(); out != `{"test":{"value":45},"test2":20}` {
t.Errorf("Incorrectly serialized: %v", out)
}
- if out := val.Search("test").String(); `{"value":45}` != out {
+ if out := val.Search("test").String(); out != `{"value":45}` {
t.Errorf("Incorrectly serialized: %v", out)
}
}
@@ -1050,19 +1069,20 @@ func TestChildrenMap(t *testing.T) {
}
for key, val := range objectMap {
- if "objectOne" == key {
+ switch key {
+ case "objectOne":
if val := val.S("num").Data().(float64); val != 1 {
t.Errorf("%v != %v", val, 1)
}
- } else if "objectTwo" == key {
+ case "objectTwo":
if val := val.S("num").Data().(float64); val != 2 {
t.Errorf("%v != %v", val, 2)
}
- } else if "objectThree" == key {
+ case "objectThree":
if val := val.S("num").Data().(float64); val != 3 {
t.Errorf("%v != %v", val, 3)
}
- } else {
+ default:
t.Errorf("Unexpected key: %v", key)
}
}
@@ -1403,7 +1423,7 @@ func TestLargeSample(t *testing.T) {
}
func TestShorthand(t *testing.T) {
- json, _ := ParseJSON([]byte(`{
+ container, _ := ParseJSON([]byte(`{
"outter":{
"inner":{
"value":5,
@@ -1418,24 +1438,24 @@ func TestShorthand(t *testing.T) {
}
}`))
- missingValue := json.S("outter").S("doesntexist").S("alsodoesntexist").S("inner").S("value").Data()
+ missingValue := container.S("outter").S("doesntexist").S("alsodoesntexist").S("inner").S("value").Data()
if missingValue != nil {
t.Errorf("missing value was actually found: %v\n", missingValue)
}
- realValue := json.S("outter").S("inner").S("value2").Data().(float64)
+ realValue := container.S("outter").S("inner").S("value2").Data().(float64)
if realValue != 10 {
t.Errorf("real value was incorrect: %v\n", realValue)
}
- _, err := json.S("outter2").Set(json.S("outter").S("inner").Data(), "inner")
+ _, err := container.S("outter2").Set(container.S("outter").S("inner").Data(), "inner")
if err != nil {
t.Errorf("error setting outter2: %v\n", err)
}
compare := `{"outter":{"inner":{"value":5,"value2":10,"value3":11},"inner2":{}}` +
`,"outter2":{"inner":{"value":5,"value2":10,"value3":11}}}`
- out := json.String()
+ out := container.String()
if out != compare {
t.Errorf("wrong serialized structure: %v\n", out)
}
@@ -1443,8 +1463,8 @@ func TestShorthand(t *testing.T) {
compare2 := `{"outter":{"inner":{"value":6,"value2":10,"value3":11},"inner2":{}}` +
`,"outter2":{"inner":{"value":6,"value2":10,"value3":11}}}`
- json.S("outter").S("inner").Set(6, "value")
- out = json.String()
+ container.S("outter").S("inner").Set(6, "value")
+ out = container.String()
if out != compare2 {
t.Errorf("wrong serialized structure: %v\n", out)
}
@@ -1475,14 +1495,14 @@ func TestInvalid(t *testing.T) {
}
invalidStr := validObj.S("Doesn't exist").String()
- if "null" != invalidStr {
+ if invalidStr != "null" {
t.Errorf("expected 'null', received: %v", invalidStr)
}
}
func TestCreation(t *testing.T) {
- json, _ := ParseJSON([]byte(`{}`))
- inner, err := json.ObjectP("test.inner")
+ container, _ := ParseJSON([]byte(`{}`))
+ inner, err := container.ObjectP("test.inner")
if err != nil {
t.Errorf("Error: %v", err)
return
@@ -1498,7 +1518,7 @@ func TestCreation(t *testing.T) {
expected := `{"test":{"inner":{"array":["first element of the array",2,"three"],` +
`"first":10,"second":20}}}`
- actual := json.String()
+ actual := container.String()
if actual != expected {
t.Errorf("received incorrect output from json object: %v\n", actual)
}
@@ -1558,9 +1578,13 @@ dynamic approach.
*/
func BenchmarkStatic(b *testing.B) {
+ b.ReportAllocs()
for i := 0; i < b.N; i++ {
var jsonObj jsonStructure
- json.Unmarshal(jsonContent, &jsonObj)
+ if err := json.Unmarshal(jsonContent, &jsonObj); err != nil {
+ b.Errorf("Error: %v", err)
+ return
+ }
if val := jsonObj.FirstOutter.SecondInner.NumberType; val != 12 {
b.Errorf("Wrong value of FirstOutter.SecondInner.NumberType: %v\n", val)
@@ -1580,6 +1604,7 @@ func BenchmarkStatic(b *testing.B) {
}
func BenchmarkDynamic(b *testing.B) {
+ b.ReportAllocs()
for i := 0; i < b.N; i++ {
jsonObj, err := ParseJSON(jsonContent)
if err != nil {
@@ -1612,10 +1637,10 @@ func TestBadIndexes(t *testing.T) {
if err != nil {
t.Error(err)
}
- if act := jsonObj.Index(0).Data(); nil != act {
+ if act := jsonObj.Index(0).Data(); act != nil {
t.Errorf("Unexpected value returned: %v != %v", nil, act)
}
- if act := jsonObj.S("array").Index(4).Data(); nil != act {
+ if act := jsonObj.S("array").Index(4).Data(); act != nil {
t.Errorf("Unexpected value returned: %v != %v", nil, act)
}
}
@@ -1808,6 +1833,18 @@ func TestFlatten(t *testing.T) {
input: `[["1"],["2","3"]]`,
output: `{"0.0":"1","1.0":"2","1.1":"3"}`,
},
+ {
+ input: `{"foo":{"bar":null}}`,
+ output: `{"foo.bar":null}`,
+ },
+ {
+ input: `{"foo":{"bar":{}}}`,
+ output: `{}`,
+ },
+ {
+ input: `{"foo":{"bar":[]}}`,
+ output: `{}`,
+ },
}
for _, test := range tests {
@@ -1825,3 +1862,91 @@ func TestFlatten(t *testing.T) {
}
}
}
+
+func TestFlattenIncludeEmpty(t *testing.T) {
+ type testCase struct {
+ input string
+ output string
+ }
+ tests := []testCase{
+ {
+ input: `{"foo":{"bar":"baz"}}`,
+ output: `{"foo.bar":"baz"}`,
+ },
+ {
+ input: `{"foo":[{"bar":"1"},{"bar":"2"}]}`,
+ output: `{"foo.0.bar":"1","foo.1.bar":"2"}`,
+ },
+ {
+ input: `[{"bar":"1"},{"bar":"2"}]`,
+ output: `{"0.bar":"1","1.bar":"2"}`,
+ },
+ {
+ input: `[["1"],["2","3"]]`,
+ output: `{"0.0":"1","1.0":"2","1.1":"3"}`,
+ },
+ {
+ input: `{"foo":{"bar":null}}`,
+ output: `{"foo.bar":null}`,
+ },
+ {
+ input: `{"foo":{"bar":{}}}`,
+ output: `{"foo.bar":{}}`,
+ },
+ {
+ input: `{"foo":{"bar":[]}}`,
+ output: `{"foo.bar":[]}`,
+ },
+ }
+
+ for _, test := range tests {
+ gObj, err := ParseJSON([]byte(test.input))
+ if err != nil {
+ t.Fatalf("Failed to parse '%v': %v", test.input, err)
+ }
+ var res map[string]interface{}
+ if res, err = gObj.FlattenIncludeEmpty(); err != nil {
+ t.Error(err)
+ continue
+ }
+ if exp, act := test.output, Wrap(res).String(); exp != act {
+ t.Errorf("Wrong result: %v != %v", act, exp)
+ }
+ }
+}
+
+func BenchmarkChildren(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ jsonObj, err := ParseJSON(bigSample)
+ if err != nil {
+ b.Errorf("Error parsing json: %v\n", err)
+ }
+
+ _ = jsonObj.Children()
+
+ FOSI := jsonObj.S("firstOutter", "secondInner")
+ _ = FOSI.Children()
+ SOSI := jsonObj.S("secondOutter", "secondInner")
+ _ = SOSI.Children()
+ SOTI := jsonObj.S("secondOutter", "thirdInner")
+ _ = SOTI.Children()
+ }
+}
+
+func BenchmarkWildcardSearch(b *testing.B) {
+ sample := []byte(`{"test":[{"value":10},{"value":20}]}`)
+
+ val, err := ParseJSON(sample)
+ if err != nil {
+ b.Fatalf("Failed to parse: %v", err)
+ }
+
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ val.Search([]string{"test", "*"}...)
+ val.Search([]string{"test", "*", "value"}...)
+ }
+}
diff --git a/go.mod b/go.mod
index c3d7ca4..bd75790 100644
--- a/go.mod
+++ b/go.mod
@@ -1 +1,3 @@
module github.com/Jeffail/gabs/v2
+
+go 1.16
Debdiff
File lists identical (after any substitutions)
No differences were encountered in the control files