Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arrayeach early termination #134

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions Dockerfile
@@ -1,12 +1,13 @@
FROM golang:1.6
FROM golang:1.7

RUN go get github.com/Jeffail/gabs
RUN go get github.com/bitly/go-simplejson
RUN go get github.com/pquerna/ffjson
RUN go get github.com/antonholmquist/jason
RUN go get github.com/mreiferson/go-ujson
RUN go get github.com/a8m/djson
RUN go get -tags=unsafe -u github.com/ugorji/go/codec
RUN go get github.com/mailru/easyjson

WORKDIR /go/src/github.com/buger/jsonparser
ADD . /go/src/github.com/buger/jsonparser
ADD . /go/src/github.com/buger/jsonparser
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -152,9 +152,9 @@ If key data type do not match, it will return error.

### **`ArrayEach`**
```go
func ArrayEach(data []byte, cb func(value []byte, dataType jsonparser.ValueType, offset int, err error), keys ...string)
func ArrayEach(data []byte, cb func(value []byte, dataType jsonparser.ValueType, offset int, err *error), keys ...string)
```
Needed for iterating arrays, accepts a callback function with the same return arguments as `Get`.
Used for iterating arrays, accepts a callback function with the same return arguments as `Get`, except for error. If error is set from within callback, ArrayEach() terminates immediately, returning the same error.

### **`ObjectEach`**
```go
Expand Down
4 changes: 2 additions & 2 deletions benchmark/benchmark_large_payload_test.go
Expand Up @@ -23,12 +23,12 @@ import (
*/
func BenchmarkJsonParserLarge(b *testing.B) {
for i := 0; i < b.N; i++ {
jsonparser.ArrayEach(largeFixture, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
jsonparser.ArrayEach(largeFixture, func(value []byte, dataType jsonparser.ValueType, offset int, err *error) {
jsonparser.Get(value, "username")
nothing()
}, "users")

jsonparser.ArrayEach(largeFixture, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
jsonparser.ArrayEach(largeFixture, func(value []byte, dataType jsonparser.ValueType, offset int, err *error) {
jsonparser.GetInt(value, "id")
jsonparser.Get(value, "slug")
nothing()
Expand Down
8 changes: 4 additions & 4 deletions benchmark/benchmark_medium_payload_test.go
Expand Up @@ -31,7 +31,7 @@ func BenchmarkJsonParserMedium(b *testing.B) {
jsonparser.GetInt(mediumFixture, "person", "github", "followers")
jsonparser.Get(mediumFixture, "company")

jsonparser.ArrayEach(mediumFixture, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
jsonparser.ArrayEach(mediumFixture, func(value []byte, dataType jsonparser.ValueType, offset int, err *error) {
jsonparser.Get(value, "url")
nothing()
}, "person", "gravatar", "avatars")
Expand Down Expand Up @@ -69,7 +69,7 @@ func BenchmarkJsonParserEachKeyManualMedium(b *testing.B) {
case 2:
// jsonparser.ParseString(value)
case 3:
jsonparser.ArrayEach(value, func(avalue []byte, dataType jsonparser.ValueType, offset int, err error) {
jsonparser.ArrayEach(value, func(avalue []byte, dataType jsonparser.ValueType, offset int, err *error) {
jsonparser.Get(avalue, "url")
})
}
Expand Down Expand Up @@ -105,7 +105,7 @@ func BenchmarkJsonParserEachKeyStructMedium(b *testing.B) {
json.Unmarshal(value, &data.Company) // we don't have a JSON -> map[string]interface{} function yet, so use standard encoding/json here
case 3:
var avatars []*CBAvatar
jsonparser.ArrayEach(value, func(avalue []byte, dataType jsonparser.ValueType, offset int, err error) {
jsonparser.ArrayEach(value, func(avalue []byte, dataType jsonparser.ValueType, offset int, err *error) {
url, _ := jsonparser.ParseString(avalue)
avatars = append(avatars, &CBAvatar{Url: url})
})
Expand Down Expand Up @@ -141,7 +141,7 @@ func BenchmarkJsonParserObjectEachStructMedium(b *testing.B) {
missing--
case bytes.Equal(k, gravatarKey):
var avatars []*CBAvatar
jsonparser.ArrayEach(v, func(avalue []byte, dataType jsonparser.ValueType, offset int, err error) {
jsonparser.ArrayEach(v, func(avalue []byte, dataType jsonparser.ValueType, offset int, err *error) {
url, _ := jsonparser.ParseString(avalue)
avatars = append(avatars, &CBAvatar{Url: url})
}, "avatars")
Expand Down
12 changes: 6 additions & 6 deletions parser.go
Expand Up @@ -283,7 +283,7 @@ func searchKeys(data []byte, keys ...string) int {
var valueFound []byte
var valueOffset int
var curI = i
ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err *error) {
if curIdx == aIdx {
valueFound = value
valueOffset = offset
Expand Down Expand Up @@ -473,7 +473,7 @@ func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]str
level++

var curIdx int
arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err *error) {
if arrIdxFlags&bitwiseFlags[curIdx+1] != 0 {
for pi, p := range paths {
if pIdxFlags&bitwiseFlags[pi+1] != 0 {
Expand Down Expand Up @@ -875,8 +875,8 @@ func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType,
return value, dataType, offset, endOffset, nil
}

// ArrayEach is used when iterating arrays, accepts a callback function with the same return arguments as `Get`.
func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error) {
// ArrayEach is used for iterating arrays, accepts a callback function with the same return arguments as `Get`, except for error. If error is set from within callback, ArrayEach() terminates immediately, returning the same error.
func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err *error), keys ...string) (offset int, err error) {
if len(data) == 0 {
return -1, MalformedObjectError
}
Expand Down Expand Up @@ -926,11 +926,11 @@ func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int
}

if t != NotExist {
cb(v, t, offset+o-len(v), e)
cb(v, t, offset+o-len(v), &e)
}

if e != nil {
break
return offset, e
}

offset += o
Expand Down
31 changes: 26 additions & 5 deletions parser_test.go
Expand Up @@ -2,6 +2,7 @@ package jsonparser

import (
"bytes"
"errors"
"fmt"
_ "fmt"
"reflect"
Expand All @@ -12,15 +13,15 @@ import (
var activeTest = ""

func toArray(data []byte) (result [][]byte) {
ArrayEach(data, func(value []byte, dataType ValueType, offset int, err error) {
ArrayEach(data, func(value []byte, dataType ValueType, offset int, err *error) {
result = append(result, value)
})

return
}

func toStringArray(data []byte) (result []string) {
ArrayEach(data, func(value []byte, dataType ValueType, offset int, err error) {
ArrayEach(data, func(value []byte, dataType ValueType, offset int, err *error) {
result = append(result, string(value))
})

Expand Down Expand Up @@ -1191,7 +1192,7 @@ func TestArrayEach(t *testing.T) {
mock := []byte(`{"a": { "b":[{"x": 1} ,{"x":2},{ "x":3}, {"x":4} ]}}`)
count := 0

ArrayEach(mock, func(value []byte, dataType ValueType, offset int, err error) {
ArrayEach(mock, func(value []byte, dataType ValueType, offset int, err *error) {
count++

switch count {
Expand All @@ -1218,11 +1219,11 @@ func TestArrayEach(t *testing.T) {
}

func TestArrayEachEmpty(t *testing.T) {
funcError := func([]byte, ValueType, int, error) { t.Errorf("Run func not allow") }
funcError := func([]byte, ValueType, int, *error) { t.Errorf("Run func not allow") }

type args struct {
data []byte
cb func(value []byte, dataType ValueType, offset int, err error)
cb func(value []byte, dataType ValueType, offset int, err *error)
keys []string
}
tests := []struct {
Expand Down Expand Up @@ -1252,6 +1253,26 @@ func TestArrayEachEmpty(t *testing.T) {
}
}

func TestArrayEachEarlyTermination(t *testing.T) {
earlyTermError := errors.New("Early termination")
mock := []byte(`{"a":[{"x":1},{"x":2},{"x":3},{"x":4}]}`)
count := 0

_, err := ArrayEach(mock, func(value []byte, dataType ValueType, offset int, err *error) {
count++

if count == 2 {
*err = earlyTermError
} else if count > 2 {
t.Errorf("Should never go beyond second item")
}
}, "a")

if err != earlyTermError {
t.Errorf("Expected error from early termination")
}
}

type keyValueEntry struct {
key string
value string
Expand Down