mirror of https://github.com/tidwall/gjson.git
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)
|
||||
|
||||
```
|
||||
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
|
||||
BenchmarkFFJSONLexer-8 1500000 3773 ns/op 1024 B/op 8 allocs/op
|
||||
BenchmarkEasyJSONLexer-8 3000000 1134 ns/op 741 B/op 6 allocs/op
|
||||
BenchmarkJSONParserGet-8 3000000 596 ns/op 21 B/op 0 allocs/op
|
||||
BenchmarkGJSONGet-8 3000000 373 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkJSONUnmarshalMap-8 600000 8884 ns/op 3048 B/op 69 allocs/op
|
||||
BenchmarkJSONUnmarshalStruct-8 600000 9045 ns/op 1832 B/op 69 allocs/op
|
||||
BenchmarkJSONDecoder-8 300000 14134 ns/op 4224 B/op 184 allocs/op
|
||||
BenchmarkFFJSONLexer-8 1500000 3182 ns/op 896 B/op 8 allocs/op
|
||||
BenchmarkEasyJSONLexer-8 3000000 932 ns/op 613 B/op 6 allocs/op
|
||||
BenchmarkJSONParserGet-8 3000000 444 ns/op 21 B/op 0 allocs/op
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
for i := 0; i < len(path); i++ {
|
||||
next_part:
|
||||
if path[i] == '\\' {
|
||||
// go into escape mode
|
||||
epart := []byte(path[s:i])
|
||||
i++
|
||||
if i < len(path) {
|
||||
epart = append(epart, path[i])
|
||||
// be optimistic that the path mostly contains lowercase and
|
||||
// underscore characters.
|
||||
if path[i] <= '\\' {
|
||||
if path[i] == '\\' {
|
||||
// go into escape mode.
|
||||
epart := []byte(path[s: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
|
||||
}
|
||||
if i < len(path) {
|
||||
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:]})
|
||||
|
@ -187,33 +191,37 @@ end_parts:
|
|||
|
||||
// look for first delimiter
|
||||
for ; i < len(json); i++ {
|
||||
if json[i] > ' ' {
|
||||
if json[i] == '{' {
|
||||
f.stype = '{'
|
||||
} else if json[i] == '[' {
|
||||
f.stype = '['
|
||||
} else {
|
||||
// not a valid type
|
||||
return Result{}
|
||||
}
|
||||
if json[i] == '{' {
|
||||
f.stype = '{'
|
||||
i++
|
||||
stack[0].stype = f.stype
|
||||
break
|
||||
} else if json[i] == '[' {
|
||||
f.stype = '['
|
||||
stack[0].stype = f.stype
|
||||
i++
|
||||
break
|
||||
} else if json[i] <= ' ' {
|
||||
continue
|
||||
} else {
|
||||
return Result{}
|
||||
}
|
||||
}
|
||||
|
||||
stack[0].stype = f.stype
|
||||
|
||||
// search for key
|
||||
// read the next key
|
||||
read_key:
|
||||
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.count++
|
||||
} else {
|
||||
// for objects we must parse the next string.
|
||||
for ; i < len(json); i++ {
|
||||
// read string
|
||||
if json[i] == '"' {
|
||||
//read to end of key
|
||||
i++
|
||||
// readstr
|
||||
// the first double-quote has already been read
|
||||
s = i
|
||||
for ; i < len(json); i++ {
|
||||
|
@ -249,16 +257,17 @@ read_key:
|
|||
}
|
||||
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?
|
||||
if parts[depth-1].wild {
|
||||
// it's a wildcard path element
|
||||
matched = wildcardMatch(f.key, parts[depth-1].key)
|
||||
} else {
|
||||
// just a straight up equality check
|
||||
matched = parts[depth-1].key == f.key
|
||||
}
|
||||
|
||||
|
@ -325,22 +334,16 @@ read_key:
|
|||
|
||||
proc_delim:
|
||||
if (matched && depth == len(parts)) || !matched {
|
||||
// -- BEGIN SQUASH -- //
|
||||
// begin squash
|
||||
// squash the value, ignoring all nested arrays and objects.
|
||||
s = i - 1
|
||||
// the first '[' or '{' has already been read
|
||||
depth := 1
|
||||
squash:
|
||||
for ; i < len(json); i++ {
|
||||
if json[i] >= '"' && json[i] <= '}' {
|
||||
if json[i] == '{' || json[i] == '[' {
|
||||
depth++
|
||||
} else if json[i] == '}' || json[i] == ']' {
|
||||
depth--
|
||||
if depth == 0 {
|
||||
i++
|
||||
break
|
||||
}
|
||||
} else if json[i] == '"' {
|
||||
switch json[i] {
|
||||
case '"':
|
||||
i++
|
||||
s2 := i
|
||||
for ; i < len(json); i++ {
|
||||
|
@ -361,14 +364,19 @@ proc_delim:
|
|||
break
|
||||
}
|
||||
}
|
||||
if i == len(json) {
|
||||
break
|
||||
case '{', '[':
|
||||
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
|
||||
|
@ -379,6 +387,7 @@ proc_val:
|
|||
switch vc {
|
||||
case '{', '[':
|
||||
value.Type = JSON
|
||||
value.Raw = json[s:i]
|
||||
case 'n':
|
||||
value.Type = Null
|
||||
case 't':
|
||||
|
|
|
@ -222,60 +222,31 @@ func TestLess(t *testing.T) {
|
|||
assert(t, stringLessInsensitive("124abcde", "125abcde"))
|
||||
}
|
||||
|
||||
/*
|
||||
func TestTwitter(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("twitter.json")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
token := Get(string(data), "search_metadata.max_id")
|
||||
if token.Num != 505874924095815700 {
|
||||
t.Fatalf("expecting %d\n", 505874924095815700)
|
||||
}
|
||||
|
||||
}
|
||||
func BenchmarkTwitter(t *testing.B) {
|
||||
// the twitter.json file must be present
|
||||
data, err := ioutil.ReadFile("twitter.json")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
json := string(data)
|
||||
t.ResetTimer()
|
||||
for i := 0; i < t.N; i++ {
|
||||
token := Get(json, "search_metadata.max_id")
|
||||
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;"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
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 {
|
||||
Widget struct {
|
||||
|
|
Loading…
Reference in New Issue