Skip to content

Commit

Permalink
Merge pull request #40 from qntfy/v3-rc1
Browse files Browse the repository at this point in the history
3.0.0 (jsonparser migration) from RC to Master
  • Loading branch information
JoshuaC215 committed Jun 21, 2017
2 parents e8f92af + 6804674 commit ae9547f
Show file tree
Hide file tree
Showing 23 changed files with 608 additions and 273 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -2,6 +2,7 @@
*.o
*.a
*.so
glide.lock

# Folders
_obj
Expand All @@ -23,5 +24,6 @@ _testmain.go
*.test
*.prof
bin/
vendor/

.idea/
2 changes: 2 additions & 0 deletions .travis.yml
Expand Up @@ -5,6 +5,8 @@ before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
- go get github.com/mitchellh/gox
- go get github.com/Masterminds/glide
- glide up
script:
- "$HOME/gopath/bin/goveralls -service=travis-ci"
- gox -output "bin/{{.Dir}}_{{.OS}}_{{.Arch}}" ./...
Expand Down
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -32,6 +32,10 @@ pull requests for new transform types so that they can be incorporated into the
but understand sometimes time-constraints or licensing issues prevent this. See the API documentation
for details on how to write and register custom transforms.

Due to performance considerations, Kazaam does not fully validate that input data is valid JSON. The
`IsJson()` function is provided for convenience if this functionality is needed, it may significantly slow
down use of Kazaam.

## Specification Support
Kazaam currently supports the following transforms:
- shift
Expand Down
4 changes: 2 additions & 2 deletions glide.yaml
@@ -1,4 +1,4 @@
package: github.com/qntfy/kazaam
import:
- package: github.com/bitly/go-simplejson
version: ^0.5.0
- package: github.com/buger/jsonparser
version: bb14bb6c38f6cf1706ef55278891d184b6a51b0e
107 changes: 64 additions & 43 deletions kazaam.go
Expand Up @@ -2,25 +2,26 @@
package kazaam

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strings"

"github.com/bitly/go-simplejson"
"github.com/buger/jsonparser"
"github.com/qntfy/kazaam/transform"
)

// TransformFunc defines the contract that any Transform function implementation
// must abide by. The transform's first argument is a `kazaam.Spec` object that
// contains any configuration necessary for the transform. The second argument
// is a `simplejson.Json` object that contains the data to be transformed.
// is a `[]byte` object that contains the data to be transformed.
//
// The data object passed in should be modified in-place. Where that is not
// possible, a new `simplejson.Json` object should be created and the pointer
// updated. The function should return an error if necessary.
// The data object passed in should be modified in-place and returned. Where
// that is not possible, a new `[]byte` object should be created and returned.
// The function should return an error if necessary.
// Transforms should strive to fail gracefully whenever possible.
type TransformFunc func(spec *transform.Config, data *simplejson.Json) error
type TransformFunc func(spec *transform.Config, data []byte) ([]byte, error)

var validSpecTypes map[string]TransformFunc

Expand Down Expand Up @@ -165,76 +166,96 @@ func transformErrorType(err error) error {
}
}

// Transform takes the *simplejson.Json `data`, transforms it according
// to the loaded spec, and returns the modified *simplejson.Json object.
func (k *Kazaam) Transform(data *simplejson.Json) (*simplejson.Json, error) {
d := simplejson.New()
d.SetPath(nil, data.Interface())
err := k.TransformInPlace(d)
// Transform makes a copy of the byte slice `data`, transforms it according
// to the loaded spec, and returns the new, modified byte slice.
func (k *Kazaam) Transform(data []byte) ([]byte, error) {
d := make([]byte, len(data))
copy(d, data)
d, err := k.TransformInPlace(d)
return d, err
}

// TransformInPlace takes the *simplejson.Json `data`, transforms it according
// to the loaded spec, and modifies the *simplejson.Json object.
// TransformInPlace takes the byte slice `data`, transforms it according
// to the loaded spec, and modifies the byte slice in place.
//
// Note: this is a destructive operation: the transformation is done in place.
// You must perform a deep copy of the data prior to calling Transform if
// You must perform a deep copy of the data prior to calling TransformInPlace if
// the original JSON object must be retained.
func (k *Kazaam) TransformInPlace(data *simplejson.Json) error {
func (k *Kazaam) TransformInPlace(data []byte) ([]byte, error) {
if k == nil || k.specJSON == nil {
return data, &Error{ErrMsg: "Kazaam not properly initialized", ErrType: SpecError}
}
if len(data) == 0 {
return data, nil
}

var err error
for _, specObj := range k.specJSON {
if specObj.Config != nil && specObj.Over != nil {
dataList := data.GetPath(strings.Split(*specObj.Over, ".")...).MustArray()

var transformedDataList []interface{}
for _, x := range dataList {
jsonValue := simplejson.New()
jsonValue.SetPath(nil, x)
intErr := k.getTransform(&specObj)(specObj.Config, jsonValue)
var transformedDataList [][]byte
_, err = jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
transformedDataList = append(transformedDataList, value)
}, strings.Split(*specObj.Over, ".")...)
if err != nil {
return data, transformErrorType(err)
}
for i, value := range transformedDataList {
x := make([]byte, len(value))
copy(x, value)
x, intErr := k.getTransform(&specObj)(specObj.Config, x)
if intErr != nil {
return transformErrorType(err)
return data, transformErrorType(err)
}
transformedDataList = append(transformedDataList, jsonValue.Interface())
transformedDataList[i] = x
}
// copy into raw []byte format and return
var buffer bytes.Buffer
buffer.WriteByte('[')
for i := 0; i < len(transformedDataList)-1; i++ {
buffer.Write(transformedDataList[i])
buffer.WriteByte(',')
}
if len(transformedDataList) > 0 {
buffer.Write(transformedDataList[len(transformedDataList)-1])
}
buffer.WriteByte(']')
data, err = jsonparser.Set(data, buffer.Bytes(), strings.Split(*specObj.Over, ".")...)
if err != nil {
return data, transformErrorType(err)
}
data.SetPath(strings.Split(*specObj.Over, "."), transformedDataList)

} else {
err = k.getTransform(&specObj)(specObj.Config, data)
data, err = k.getTransform(&specObj)(specObj.Config, data)
if err != nil {
return transformErrorType(err)
return data, transformErrorType(err)
}
}
}
return transformErrorType(err)
return data, transformErrorType(err)
}

// TransformJSONStringToString loads the JSON string `data`, transforms
// it as per the spec, and returns the transformed JSON string.
func (k *Kazaam) TransformJSONStringToString(data string) (string, error) {
// read in the arbitrary input data
d, err := simplejson.NewJson([]byte(data))
if err != nil {
return "", err
}
err = k.TransformInPlace(d)
d, err := k.TransformJSONString(data)
if err != nil {
return "", err
}
jsonString, _ := d.MarshalJSON()
return string(jsonString), nil
return string(d), nil
}

// TransformJSONString loads the JSON string, transforms it as per the
// spec, and returns a pointer to a transformed simplejson.Json.
// spec, and returns a pointer to a transformed []byte.
//
// This function is especially useful when one may need to extract
// multiple fields from the transformed JSON.
func (k *Kazaam) TransformJSONString(data string) (*simplejson.Json, error) {
func (k *Kazaam) TransformJSONString(data string) ([]byte, error) {
// read in the arbitrary input data
d, err := simplejson.NewJson([]byte(data))
d := make([]byte, len(data))
copy(d, []byte(data))
d, err := k.TransformInPlace(d)
if err != nil {
return nil, err
return []byte{}, err
}
k.TransformInPlace(d)
return d, nil
return d, err
}
16 changes: 16 additions & 0 deletions kazaam_benchmarks_test.go
Expand Up @@ -10,6 +10,10 @@ const (
benchmarkJSON = `{"topKeyA": {"arrayKey": [{"foo": 0}, {"foo": 1}, {"foo": 1}, {"foo": 2}], "notArrayKey": "bar", "deepArrayKey": [{"key0":["val0", "val1"]}]}, "topKeyB":{"nextKeyB": "valueB"}}`
)

var (
benchmarkSlice = []byte(benchmarkJSON)
)

// Just for emulating field access, so it will not throw "evaluated but not
// used." Borrowed from:
// https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_small_payload_test.go
Expand Down Expand Up @@ -265,3 +269,15 @@ func BenchmarkExtractTransformOnly(b *testing.B) {
}

}

func BenchmarkIsJsonFast(b *testing.B) {
b.ReportAllocs()

b.ResetTimer()

for i := 0; i < b.N; i++ {
val := kazaam.IsJsonFast(benchmarkSlice)
nothing(val)
}

}

0 comments on commit ae9547f

Please sign in to comment.