forked from mirror/gjson
optimistic optimizations
This commit is contained in:
parent
a721b7ad07
commit
0e6e567424
16
README.md
16
README.md
|
@ -129,13 +129,13 @@ Benchmarks of GJSON alongside [encoding/json](https://golang.org/pkg/encoding/js
|
||||||
and [jsonparser](https://github.com/buger/jsonparser)
|
and [jsonparser](https://github.com/buger/jsonparser)
|
||||||
|
|
||||||
```
|
```
|
||||||
BenchmarkGJSONGet-8 3000000 440 ns/op 0 B/op 0 allocs/op
|
BenchmarkGJSONGet-8 3000000 373 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkJSONUnmarshalMap-8 600000 10738 ns/op 3176 B/op 69 allocs/op
|
BenchmarkJSONUnmarshalMap-8 600000 8884 ns/op 3048 B/op 69 allocs/op
|
||||||
BenchmarkJSONUnmarshalStruct-8 600000 11635 ns/op 1960 B/op 69 allocs/op
|
BenchmarkJSONUnmarshalStruct-8 600000 9045 ns/op 1832 B/op 69 allocs/op
|
||||||
BenchmarkJSONDecoder-8 300000 17193 ns/op 4864 B/op 184 allocs/op
|
BenchmarkJSONDecoder-8 300000 14134 ns/op 4224 B/op 184 allocs/op
|
||||||
BenchmarkFFJSONLexer-8 1500000 3773 ns/op 1024 B/op 8 allocs/op
|
BenchmarkFFJSONLexer-8 1500000 3182 ns/op 896 B/op 8 allocs/op
|
||||||
BenchmarkEasyJSONLexer-8 3000000 1134 ns/op 741 B/op 6 allocs/op
|
BenchmarkEasyJSONLexer-8 3000000 932 ns/op 613 B/op 6 allocs/op
|
||||||
BenchmarkJSONParserGet-8 3000000 596 ns/op 21 B/op 0 allocs/op
|
BenchmarkJSONParserGet-8 3000000 444 ns/op 21 B/op 0 allocs/op
|
||||||
```
|
```
|
||||||
|
|
||||||
JSON document used:
|
JSON document used:
|
||||||
|
@ -177,7 +177,7 @@ widget.text.onMouseUp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
*These are the results from running the benchmarks on a MacBook Pro 15" 2.8 GHz Intel Core i7:*
|
*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7.*
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
||||||
|
|
137
gjson.go
137
gjson.go
|
@ -133,44 +133,48 @@ func Get(json string, path string) Result {
|
||||||
// parse the path. just split on the dot
|
// parse the path. just split on the dot
|
||||||
for i := 0; i < len(path); i++ {
|
for i := 0; i < len(path); i++ {
|
||||||
next_part:
|
next_part:
|
||||||
if path[i] == '\\' {
|
// be optimistic that the path mostly contains lowercase and
|
||||||
// go into escape mode
|
// underscore characters.
|
||||||
epart := []byte(path[s:i])
|
if path[i] <= '\\' {
|
||||||
i++
|
if path[i] == '\\' {
|
||||||
if i < len(path) {
|
// go into escape mode.
|
||||||
epart = append(epart, path[i])
|
epart := []byte(path[s:i])
|
||||||
i++
|
i++
|
||||||
for ; i < len(path); i++ {
|
if i < len(path) {
|
||||||
if path[i] == '\\' {
|
|
||||||
i++
|
|
||||||
if i < len(path) {
|
|
||||||
epart = append(epart, path[i])
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else if path[i] == '.' {
|
|
||||||
parts = append(parts, part{wild: wild, key: string(epart)})
|
|
||||||
if wild {
|
|
||||||
wild = false
|
|
||||||
}
|
|
||||||
s = i + 1
|
|
||||||
i++
|
|
||||||
goto next_part
|
|
||||||
} else if path[i] == '*' || path[i] == '?' {
|
|
||||||
wild = true
|
|
||||||
}
|
|
||||||
epart = append(epart, path[i])
|
epart = append(epart, path[i])
|
||||||
|
i++
|
||||||
|
for ; i < len(path); i++ {
|
||||||
|
if path[i] == '\\' {
|
||||||
|
i++
|
||||||
|
if i < len(path) {
|
||||||
|
epart = append(epart, path[i])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if path[i] == '.' {
|
||||||
|
parts = append(parts, part{wild: wild, key: string(epart)})
|
||||||
|
if wild {
|
||||||
|
wild = false
|
||||||
|
}
|
||||||
|
s = i + 1
|
||||||
|
i++
|
||||||
|
goto next_part
|
||||||
|
} else if path[i] == '*' || path[i] == '?' {
|
||||||
|
wild = true
|
||||||
|
}
|
||||||
|
epart = append(epart, path[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
parts = append(parts, part{wild: wild, key: string(epart)})
|
||||||
|
goto end_parts
|
||||||
|
} else 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: string(epart)})
|
|
||||||
goto end_parts
|
|
||||||
} else 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:]})
|
parts = append(parts, part{wild: wild, key: path[s:]})
|
||||||
|
@ -187,33 +191,37 @@ end_parts:
|
||||||
|
|
||||||
// look for first delimiter
|
// look for first delimiter
|
||||||
for ; i < len(json); i++ {
|
for ; i < len(json); i++ {
|
||||||
if json[i] > ' ' {
|
if json[i] == '{' {
|
||||||
if json[i] == '{' {
|
f.stype = '{'
|
||||||
f.stype = '{'
|
i++
|
||||||
} else if json[i] == '[' {
|
stack[0].stype = f.stype
|
||||||
f.stype = '['
|
break
|
||||||
} else {
|
} else if json[i] == '[' {
|
||||||
// not a valid type
|
f.stype = '['
|
||||||
return Result{}
|
stack[0].stype = f.stype
|
||||||
}
|
|
||||||
i++
|
i++
|
||||||
break
|
break
|
||||||
|
} else if json[i] <= ' ' {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return Result{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stack[0].stype = f.stype
|
// read the next key
|
||||||
|
|
||||||
// search for key
|
|
||||||
read_key:
|
read_key:
|
||||||
if f.stype == '[' {
|
if f.stype == '[' {
|
||||||
|
// for arrays we use the index of the value as the key.
|
||||||
|
// so "0" is the key for the first value, and "10" is the
|
||||||
|
// key for the 10th value.
|
||||||
f.key = strconv.FormatInt(int64(f.count), 10)
|
f.key = strconv.FormatInt(int64(f.count), 10)
|
||||||
f.count++
|
f.count++
|
||||||
} else {
|
} else {
|
||||||
|
// for objects we must parse the next string.
|
||||||
for ; i < len(json); i++ {
|
for ; i < len(json); i++ {
|
||||||
|
// read string
|
||||||
if json[i] == '"' {
|
if json[i] == '"' {
|
||||||
//read to end of key
|
|
||||||
i++
|
i++
|
||||||
// readstr
|
|
||||||
// the first double-quote has already been read
|
// the first double-quote has already been read
|
||||||
s = i
|
s = i
|
||||||
for ; i < len(json); i++ {
|
for ; i < len(json); i++ {
|
||||||
|
@ -249,16 +257,17 @@ read_key:
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
// end read string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// end readstr
|
|
||||||
|
|
||||||
// we have a brand new key.
|
// we have a brand new (possibly shiny) key.
|
||||||
// is it the key that we are looking for?
|
// is it the key that we are looking for?
|
||||||
if parts[depth-1].wild {
|
if parts[depth-1].wild {
|
||||||
// it's a wildcard path element
|
// it's a wildcard path element
|
||||||
matched = wildcardMatch(f.key, parts[depth-1].key)
|
matched = wildcardMatch(f.key, parts[depth-1].key)
|
||||||
} else {
|
} else {
|
||||||
|
// just a straight up equality check
|
||||||
matched = parts[depth-1].key == f.key
|
matched = parts[depth-1].key == f.key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,22 +334,16 @@ read_key:
|
||||||
|
|
||||||
proc_delim:
|
proc_delim:
|
||||||
if (matched && depth == len(parts)) || !matched {
|
if (matched && depth == len(parts)) || !matched {
|
||||||
// -- BEGIN SQUASH -- //
|
// begin squash
|
||||||
// squash the value, ignoring all nested arrays and objects.
|
// squash the value, ignoring all nested arrays and objects.
|
||||||
s = i - 1
|
s = i - 1
|
||||||
// the first '[' or '{' has already been read
|
// the first '[' or '{' has already been read
|
||||||
depth := 1
|
depth := 1
|
||||||
|
squash:
|
||||||
for ; i < len(json); i++ {
|
for ; i < len(json); i++ {
|
||||||
if json[i] >= '"' && json[i] <= '}' {
|
if json[i] >= '"' && json[i] <= '}' {
|
||||||
if json[i] == '{' || json[i] == '[' {
|
switch json[i] {
|
||||||
depth++
|
case '"':
|
||||||
} else if json[i] == '}' || json[i] == ']' {
|
|
||||||
depth--
|
|
||||||
if depth == 0 {
|
|
||||||
i++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if json[i] == '"' {
|
|
||||||
i++
|
i++
|
||||||
s2 := i
|
s2 := i
|
||||||
for ; i < len(json); i++ {
|
for ; i < len(json); i++ {
|
||||||
|
@ -361,14 +364,19 @@ proc_delim:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i == len(json) {
|
case '{', '[':
|
||||||
break
|
depth++
|
||||||
|
case '}', ']':
|
||||||
|
depth--
|
||||||
|
if depth == 0 {
|
||||||
|
i++
|
||||||
|
break squash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value.Raw = json[s:i]
|
// end squash
|
||||||
// -- END SQUASH -- //
|
// the 'i' and 's' values should fall-though to the proc_val function
|
||||||
}
|
}
|
||||||
|
|
||||||
// process the value
|
// process the value
|
||||||
|
@ -379,6 +387,7 @@ proc_val:
|
||||||
switch vc {
|
switch vc {
|
||||||
case '{', '[':
|
case '{', '[':
|
||||||
value.Type = JSON
|
value.Type = JSON
|
||||||
|
value.Raw = json[s:i]
|
||||||
case 'n':
|
case 'n':
|
||||||
value.Type = Null
|
value.Type = Null
|
||||||
case 't':
|
case 't':
|
||||||
|
|
|
@ -222,60 +222,31 @@ func TestLess(t *testing.T) {
|
||||||
assert(t, stringLessInsensitive("124abcde", "125abcde"))
|
assert(t, stringLessInsensitive("124abcde", "125abcde"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
var exampleJSON = `{
|
||||||
func TestTwitter(t *testing.T) {
|
"widget": {
|
||||||
data, err := ioutil.ReadFile("twitter.json")
|
"debug": "on",
|
||||||
if err != nil {
|
"window": {
|
||||||
return
|
"title": "Sample Konfabulator Widget",
|
||||||
}
|
"name": "main_window",
|
||||||
token := Get(string(data), "search_metadata.max_id")
|
"width": 500,
|
||||||
if token.Num != 505874924095815700 {
|
"height": 500
|
||||||
t.Fatalf("expecting %d\n", 505874924095815700)
|
},
|
||||||
}
|
"image": {
|
||||||
|
"src": "Images/Sun.png",
|
||||||
}
|
"hOffset": 250,
|
||||||
func BenchmarkTwitter(t *testing.B) {
|
"vOffset": 250,
|
||||||
// the twitter.json file must be present
|
"alignment": "center"
|
||||||
data, err := ioutil.ReadFile("twitter.json")
|
},
|
||||||
if err != nil {
|
"text": {
|
||||||
return
|
"data": "Click Here",
|
||||||
}
|
"size": 36,
|
||||||
json := string(data)
|
"style": "bold",
|
||||||
t.ResetTimer()
|
"vOffset": 100,
|
||||||
for i := 0; i < t.N; i++ {
|
"alignment": "center",
|
||||||
token := Get(json, "search_metadata.max_id")
|
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
|
||||||
if token.Type != Number || token.Raw != "505874924095815700" || token.Num != 505874924095815700 {
|
|
||||||
t.Fatal("invalid response")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}`
|
||||||
*/
|
|
||||||
|
|
||||||
var exampleJSON = `
|
|
||||||
{"widget": {
|
|
||||||
"debug": "on",
|
|
||||||
"window": {
|
|
||||||
"title": "Sample Konfabulator Widget",
|
|
||||||
"name": "main_window",
|
|
||||||
"width": 500,
|
|
||||||
"height": 500
|
|
||||||
},
|
|
||||||
"image": {
|
|
||||||
"src": "Images/Sun.png",
|
|
||||||
"hOffset": 250,
|
|
||||||
"vOffset": 250,
|
|
||||||
"alignment": "center"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"data": "Click Here",
|
|
||||||
"size": 36,
|
|
||||||
"style": "bold",
|
|
||||||
"vOffset": 100,
|
|
||||||
"alignment": "center",
|
|
||||||
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
`
|
|
||||||
|
|
||||||
type BenchStruct struct {
|
type BenchStruct struct {
|
||||||
Widget struct {
|
Widget struct {
|
||||||
|
|
Loading…
Reference in New Issue