diff --git a/README.md b/README.md index c260fee..204279e 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ result.Raw // holds the raw json Benchmarks of GJSON alongside [encoding/json](https://golang.org/pkg/encoding/json/), [ffjson](https://github.com/pquerna/ffjson), and [EasyJSON](https://github.com/mailru/easyjson). ``` -BenchmarkGJSONGet-8 3000000 477 ns/op 0 B/op 0 allocs/op +BenchmarkGJSONGet-8 3000000 440 ns/op 0 B/op 0 allocs/op BenchmarkJSONUnmarshalMap-8 600000 10738 ns/op 3176 B/op 69 allocs/op BenchmarkJSONUnmarshalStruct-8 600000 11635 ns/op 1960 B/op 69 allocs/op BenchmarkJSONDecoder-8 300000 17193 ns/op 4864 B/op 184 allocs/op diff --git a/gjson.go b/gjson.go index 1d9045c..3be93fc 100644 --- a/gjson.go +++ b/gjson.go @@ -76,6 +76,17 @@ func (t Result) Value() interface{} { } } +type part struct { + wild bool + key string +} + +type frame struct { + key string + count int + stype byte +} + // 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. @@ -99,33 +110,44 @@ func (t Result) Value() interface{} { // "c?ildren.0" >> "Sara" // func Get(json string, path string) Result { - var i, s, depth int - var squashed string - var key string - var stype byte - var count int + var s int var wild bool - var matched bool - var parts = make([]string, 0, 4) - var wilds = make([]bool, 0, 4) - var keys = make([]string, 0, 4) - var stypes = make([]byte, 0, 4) - var counts = make([]int, 0, 4) + var parts = make([]part, 0, 4) // do nothing when no path specified if len(path) == 0 { return Result{} // nothing } + // parse the path. just split on the dot + for i := 0; i < len(path); i++ { + if path[i] == '.' { + parts = append(parts, part{wild: wild, key: path[s:i]}) + if wild { + wild = false + } + s = i + 1 + } else if path[i] == '*' || path[i] == '?' { + wild = true + } + } + parts = append(parts, part{wild: wild, key: path[s:]}) + + var i, depth int + var squashed string + var f frame + var matched bool + var stack = make([]frame, 0, 4) + depth = 1 // look for first delimiter for ; i < len(json); i++ { if json[i] > ' ' { if json[i] == '{' { - stype = '{' + f.stype = '{' } else if json[i] == '[' { - stype = '[' + f.stype = '[' } else { // not a valid type return Result{} @@ -135,30 +157,13 @@ func Get(json string, path string) Result { } } - stypes = append(stypes, stype) - counts = append(counts, count) - - // parse the path. just split on the dot - for i := 0; i < len(path); i++ { - if path[i] == '.' { - parts = append(parts, path[s:i]) - wilds = append(wilds, wild) - if wild { - wild = false - } - s = i + 1 - } else if path[i] == '*' || path[i] == '?' { - wild = true - } - } - parts = append(parts, path[s:]) - wilds = append(wilds, wild) + stack = append(stack, f) // search for key read_key: - if stype == '[' { - key = strconv.FormatInt(int64(count), 10) - count++ + if f.stype == '[' { + f.key = strconv.FormatInt(int64(f.count), 10) + f.count++ } else { for ; i < len(json); i++ { if json[i] == '"' { @@ -169,7 +174,7 @@ read_key: s = i for ; i < len(json); i++ { if json[i] == '"' { - key = json[s:i] + f.key = json[s:i] i++ break } @@ -193,7 +198,7 @@ read_key: break } } - key = unescape(json[s:i]) + f.key = unescape(json[s:i]) i++ break } @@ -206,11 +211,11 @@ read_key: // we have a brand new key. // is it the key that we are looking for? - if wilds[depth-1] { + if parts[depth-1].wild { // it's a wildcard path element - matched = wildcardMatch(key, parts[depth-1]) + matched = wildcardMatch(f.key, parts[depth-1].key) } else { - matched = parts[depth-1] == key + matched = parts[depth-1].key == f.key } // read to the value token @@ -377,10 +382,8 @@ proc_val: // can only deep search objects // return Result{} } else { - stype = vc - keys = append(keys, key) - stypes = append(stypes, stype) - counts = append(counts, count) + f.stype = vc + stack = append(stack, f) depth++ goto read_key } @@ -416,19 +419,16 @@ proc_val: for ; i < len(json); i++ { switch json[i] { case '}', ']': - if parts[depth-1] == "#" { - return Result{Type: Number, Num: float64(count)} + if parts[depth-1].key == "#" { + return Result{Type: Number, Num: float64(f.count)} } // step the stack back depth-- if depth == 0 { return Result{} } - keys = keys[:len(keys)-1] - stypes = stypes[:len(stypes)-1] - counts = counts[:len(counts)-1] - stype = stypes[len(stypes)-1] - count = counts[len(counts)-1] + stack = stack[:len(stack)-1] + f = stack[len(stack)-1] case ',': i++ goto read_key