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

Struct unmarshmaling and more #29

Open
wants to merge 6 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,31 @@ SOURCE_PATH = /go/src/github.com/buger/jsonparser
BENCHMARK = JsonParser
BENCHTIME = 5s
TEST = .
DRUN = docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER)

build:
docker build -t $(CONTAINER) .

race:
docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) --env GORACE="halt_on_error=1" go test ./. $(ARGS) -v -race -timeout 15s
$(DRUN) --env GORACE="halt_on_error=1" go test ./. $(ARGS) -v -race -timeout 15s

bench:
docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -benchtime $(BENCHTIME) -v
$(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -benchtime $(BENCHTIME) -v

profile:
docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -memprofile mem.mprof -v
docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -cpuprofile cpu.out -v
docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -c
$(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -memprofile mem.mprof -v
$(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -cpuprofile cpu.out -v
$(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -c

test:
docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) go test $(LDFLAGS) ./ -run $(TEST) -timeout 10s $(ARGS) -v
$(DRUN) go test $(LDFLAGS) ./ -run $(TEST) -timeout 10s $(ARGS) -v

fmt:
docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) go fmt ./...
$(DRUN) go fmt ./...

vet:
docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) go vet ./.
$(DRUN) go vet ./.


bash:
docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) /bin/bash
$(DRUN) /bin/bash
44 changes: 44 additions & 0 deletions benchmark/benchmark_large_payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,50 @@ func BenchmarkJsonParserLarge(b *testing.B) {
}
}


func BenchmarkJsonParserOffsetsLarge(b *testing.B) {
keys := [][]string{
[]string{"users"},
[]string{"topics", "topics"},
}

nestedKeys := [][]string{
[]string{"id"},
[]string{"slug"},
}

for i := 0; i < b.N; i++ {
r := largeFixture
jsonparser.KeyEach(r, func (idx int, value []byte) (offset int) {
switch idx {
case 0: // users
aOff, _ := jsonparser.ArrayEach(value, func(value []byte, dataType int, offset int, err error) {
jsonparser.Get(value, "username")
nothing()
})
return aOff
case 1: // topics
aOff, _ := jsonparser.ArrayEach(value, func(value []byte, dataType int, offset int, err error) {
jsonparser.KeyEach(value, func(nidx int, value []byte)(offset int) {
v, _, of, _ := jsonparser.Get(value)
panic(string(v))
switch nidx {
case 0:
jsonparser.ParseInt(v)
case 1:
nothing(v)
}

return of
}, nestedKeys...)
})
return aOff
}
return
}, keys...)
}
}

/*
encoding/json
*/
Expand Down
47 changes: 46 additions & 1 deletion benchmark/benchmark_medium_payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/pquerna/ffjson/ffjson"
"github.com/ugorji/go/codec"
"testing"
// "fmt"
_ "fmt"
)

/*
Expand All @@ -34,6 +34,51 @@ func BenchmarkJsonParserMedium(b *testing.B) {
}
}

func BenchmarkJsonParserOffsetsMedium(b *testing.B) {
keys := [][]string{
[]string{"person", "name", "fullName"},
[]string{"person", "github", "followers"},
[]string{"company"},
[]string{"person", "gravatar", "avatars"},
}

for i := 0; i < b.N; i++ {
r := mediumFixture
jsonparser.KeyEach(r, func (idx int, value []byte) (offset int) {
v, _, offset, _ := jsonparser.Get(value)

switch idx {
case 0: // fullName
nothing(v)
case 1: // followers
jsonparser.ParseInt(value)
case 2: // company
jsonparser.Get(value)
case 3: // Processing array
aOff, _ := jsonparser.ArrayEach(value, func(value []byte, dataType int, offset int, err error) {
jsonparser.Get(value, "url")
})
offset += aOff
}
return
}, keys...)
}
}

func BenchmarkJsonParserStructMedium(b *testing.B) {
for i := 0; i < b.N; i++ {
var data MediumPayload
jsonparser.Unmarshal(mediumFixture, &data)

nothing(data.Person.Name.FullName, data.Person.Github.Followers, data.Company)

for _, el := range data.Person.Gravatar.Avatars {
nothing(el.Url)
}
}
}


/*
encoding/json
*/
Expand Down
28 changes: 28 additions & 0 deletions benchmark/benchmark_small_payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,34 @@ func BenchmarkJsonParserSmall(b *testing.B) {
}
}

func BenchmarkJsonParserSmallOffsets(b *testing.B) {
for i := 0; i < b.N; i++ {
r := smallFixture
offsets := jsonparser.KeyOffsets(r,
[]string{"uuid"},
[]string{"tz"},
[]string{"ua"},
[]string{"st"},
)

jsonparser.Get(r[offsets[0]:])
jsonparser.GetInt(r[offsets[1]:])
jsonparser.Get(r[offsets[2]:])
jsonparser.GetInt(r[offsets[3]:])

nothing()
}
}

func BenchmarkJsonParserSmallStruct(b *testing.B) {
for i := 0; i < b.N; i++ {
var data SmallPayload
jsonparser.Unmarshal(smallFixture, &data)
nothing(data.Uuid, data.Tz, data.Ua, data.St)
}
}


/*
encoding/json
*/
Expand Down
123 changes: 123 additions & 0 deletions encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package jsonparser

import (
"reflect"
"strings"
_ "fmt"
)

type structCache struct {
fields [][]string
fieldTypes []reflect.Kind
}

var cache map[string]*structCache

func init() {
cache = make(map[string]*structCache)
}

func unmarshalValue(data []byte, val reflect.Value) int {
if !val.IsValid() || !val.CanSet() {
return 0
}

v, dt, of, err := Get(data)

switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if dt == Number && err == nil {
val.SetInt(ParseInt(v))
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if dt == Number && err == nil {
val.SetUint(uint64(ParseInt(v)))
}
case reflect.String:
if dt == String && err == nil {
val.SetString(unsafeBytesToString(v))
}
case reflect.Bool:
if dt == Boolean && err == nil {
val.SetBool(ParseBoolean(v))
}
case reflect.Ptr:
obj := reflect.New(val.Type().Elem())
unmarshalValue(v, obj.Elem())
val.Set(obj)
case reflect.Struct:
obj := reflect.New(val.Type())
unmarshalStruct(v, obj.Elem())
val.Set(obj.Elem())
case reflect.Slice:
sT := val.Type().Elem()
s := reflect.MakeSlice(val.Type(), 0, 0)
ArrayEach(v, func(value []byte, dataType int, offset int, err error){
el := reflect.New(sT)

switch sT.Kind() {
case reflect.Struct:
unmarshalStruct(value, el.Elem())
case reflect.Ptr:
unmarshalValue(value, el.Elem())
default:
unmarshalValue(value, el.Elem())
}

s = reflect.Append(s, el.Elem())
})
val.Set(s)
}

return of
}

func unmarshalStruct(data []byte, val reflect.Value) int {
sName := val.Type().Name()
var sCache *structCache
var ok bool

// Cache struct info
if sCache, ok = cache[sName]; !ok {
count := val.NumField()
fields := make([][]string, count)
// fieldTypes := make([]reflect.Kind, count)

for i := 0; i < val.NumField(); i++ {
// valueField := val.Field(i)
typeField := val.Type().Field(i)
tag := typeField.Tag
jsonKey := tag.Get("json")

if jsonKey != "" {
fields[i] = []string{jsonKey}
} else {
fields[i] = []string{strings.ToLower(string(typeField.Name[:1])) + string(typeField.Name[1:])}
}
// fieldTypes[i] = valueField.Kind()
}

sCache = &structCache{fields: fields}
cache[sName] = sCache
}

fields := sCache.fields
// fieldTypes := sCache.fieldTypes

offset := KeyEach(data, func(i int, d []byte) int {
f := val.Field(i)

return unmarshalValue(d, f)
}, fields...)
// panic(string(data))

return offset
}

func Unmarshal(data []byte, v interface{}) error {
val := reflect.ValueOf(v).Elem()

unmarshalStruct(data, val)

return nil
}
Loading