From 124a25db9e41cf2a8ba27cdda5efb2d30513238f Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Thu, 8 Sep 2016 08:08:53 -0700 Subject: [PATCH] added direct bytes interface --- gjson.go | 21 +++++++++++++++--- gjson_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/gjson.go b/gjson.go index 4b73610..6c5a718 100644 --- a/gjson.go +++ b/gjson.go @@ -3,6 +3,7 @@ package gjson import ( "strconv" + "unsafe" "github.com/tidwall/match" ) @@ -1080,6 +1081,11 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { return i, false } +type parseContext struct { + json string + value Result +} + // Get searches json for the specified path. // A path is in dot syntax, such as "name.last" or "age". // This function expects that the json is well-formed, and does not validate. @@ -1127,9 +1133,18 @@ func Get(json, path string) Result { return c.value } -type parseContext struct { - json string - value Result +// GetBytes searches json for the specified path. +// If working with bytes, this method preferred over Get(string(data), path) +func GetBytes(json []byte, path string) Result { + var result Result + if json != nil { + // unsafe cast to string + result = Get(*(*string)(unsafe.Pointer(&json)), path) + // copy of string data for safety + result.Raw = string(*(*[]byte)(unsafe.Pointer(&result.Raw))) + result.Str = string(*(*[]byte)(unsafe.Pointer(&result.Str))) + } + return result } // unescape unescapes a string diff --git a/gjson_test.go b/gjson_test.go index cd97908..a8f642e 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -35,7 +35,7 @@ func TestRandomData(t *testing.T) { t.Fatal(err) } lstr = string(b[:n]) - Get(lstr, "zzzz") + GetBytes([]byte(lstr), "zzzz") } } @@ -125,6 +125,21 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"}, ] } }` +var basicJSONB = []byte(basicJSON) + +func TestByteSafety(t *testing.T) { + jsonb := []byte(`{"name":"Janet"}`) + mtok := GetBytes(jsonb, "name") + if mtok.String() != "Janet" { + t.Fatalf("expected %v, got %v", "Jason", mtok.String()) + } + jsonb[9] = 'T' + jsonb[12] = 'd' + jsonb[13] = 'y' + if mtok.String() != "Janet" { + t.Fatalf("expected %v, got %v", "Jason", mtok.String()) + } +} func TestBasic(t *testing.T) { var mtok Result @@ -134,6 +149,11 @@ func TestBasic(t *testing.T) { t.Fatalf("expected %v, got %v", "1002,3", mtok.String()) } + mtok = GetBytes(basicJSONB, `loggy.programmers.#[age=101].firstName`) + if mtok.String() != "1002.3" { + t.Fatalf("expected %v, got %v", "1002,3", mtok.String()) + } + mtok = Get(basicJSON, `loggy.programmers.#[firstName == "Brett"].email`) if mtok.String() != "aaaa" { t.Fatalf("expected %v, got %v", "aaaa", mtok.String()) @@ -740,3 +760,41 @@ func BenchmarkJSONParserGet(t *testing.B) { } t.N *= len(benchPaths) // because we are running against 3 paths } + +var massiveJSON = func() string { + var buf bytes.Buffer + buf.WriteString("[") + for i := 0; i < 100; i++ { + if i > 0 { + buf.WriteByte(',') + } + buf.WriteString(exampleJSON) + } + buf.WriteString("]") + return buf.String() +}() + +func BenchmarkConvertNone(t *testing.B) { + json := massiveJSON + t.ReportAllocs() + t.ResetTimer() + for i := 0; i < t.N; i++ { + Get(json, "50.widget.text.onMouseUp") + } +} +func BenchmarkConvertGet(t *testing.B) { + data := []byte(massiveJSON) + t.ReportAllocs() + t.ResetTimer() + for i := 0; i < t.N; i++ { + Get(string(data), "50.widget.text.onMouseUp") + } +} +func BenchmarkConvertGetBytes(t *testing.B) { + data := []byte(massiveJSON) + t.ReportAllocs() + t.ResetTimer() + for i := 0; i < t.N; i++ { + GetBytes(data, "50.widget.text.onMouseUp") + } +}