Skip to content

Commit d79e320

Browse files
committed
- Add test and benchmarks for delete method
- Fix TestObjectEach - Revert changes to bytes methods
1 parent 3e35a05 commit d79e320

File tree

8 files changed

+316
-76
lines changed

8 files changed

+316
-76
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
*.mprof
77

88
vendor/github.com/buger/goterm/
9+
prof.cpu
10+
prof.mem

benchmark/benchmark_medium_payload_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package benchmark
66

77
import (
88
"encoding/json"
9+
"testing"
10+
911
"github.com/Jeffail/gabs"
1012
"github.com/a8m/djson"
1113
"github.com/antonholmquist/jason"
@@ -15,7 +17,6 @@ import (
1517
"github.com/mreiferson/go-ujson"
1618
"github.com/pquerna/ffjson/ffjson"
1719
"github.com/ugorji/go/codec"
18-
"testing"
1920
// "fmt"
2021
"bytes"
2122
"errors"
@@ -37,6 +38,19 @@ func BenchmarkJsonParserMedium(b *testing.B) {
3738
}
3839
}
3940

41+
func BenchmarkJsonParserDelMedium(b *testing.B) {
42+
fixture := make([]byte, 0, len(mediumFixture))
43+
b.ResetTimer()
44+
for i := 0; i < b.N; i++ {
45+
fixture = append(fixture[:0], mediumFixture...)
46+
fixture = jsonparser.Del(fixture, "person", "name", "fullName")
47+
fixture = jsonparser.Del(fixture, "person", "github", "followers")
48+
fixture = jsonparser.Del(fixture, "company")
49+
50+
nothing()
51+
}
52+
}
53+
4054
func BenchmarkJsonParserEachKeyManualMedium(b *testing.B) {
4155
paths := [][]string{
4256
[]string{"person", "name", "fullName"},

benchmark/benchmark_small_payload_test.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package benchmark
66

77
import (
88
"encoding/json"
9+
"testing"
10+
911
"github.com/Jeffail/gabs"
1012
"github.com/a8m/djson"
1113
"github.com/antonholmquist/jason"
@@ -15,7 +17,6 @@ import (
1517
"github.com/mreiferson/go-ujson"
1618
"github.com/pquerna/ffjson/ffjson"
1719
"github.com/ugorji/go/codec"
18-
"testing"
1920
// "fmt"
2021
"bytes"
2122
"errors"
@@ -141,6 +142,20 @@ func BenchmarkJsonParserSetSmall(b *testing.B) {
141142
}
142143
}
143144

145+
func BenchmarkJsonParserDelSmall(b *testing.B) {
146+
fixture := make([]byte, 0, len(smallFixture))
147+
b.ResetTimer()
148+
for i := 0; i < b.N; i++ {
149+
fixture = append(fixture[:0], smallFixture...)
150+
fixture = jsonparser.Del(fixture, "uuid")
151+
fixture = jsonparser.Del(fixture, "tz")
152+
fixture = jsonparser.Del(fixture, "ua")
153+
fixture = jsonparser.Del(fixture, "stt")
154+
155+
nothing()
156+
}
157+
}
158+
144159
/*
145160
encoding/json
146161
*/

bytes_safe.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ func parseFloat(b *[]byte) (float64, error) {
1919
func bytesToString(b *[]byte) string {
2020
return string(*b)
2121
}
22+
23+
func StringToBytes(s string) []byte {
24+
return []byte(s)
25+
}

bytes_unsafe.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package jsonparser
44

55
import (
6+
"reflect"
67
"strconv"
78
"unsafe"
89
)
@@ -16,16 +17,26 @@ import (
1617
//
1718
// TODO: Remove hack after Go 1.7 release
1819
//
19-
func equalStr(b []byte, s string) bool {
20-
return *(*string)(unsafe.Pointer(&b)) == s
20+
func equalStr(b *[]byte, s string) bool {
21+
return *(*string)(unsafe.Pointer(b)) == s
2122
}
2223

23-
func parseFloat(b []byte) (float64, error) {
24-
return strconv.ParseFloat(*(*string)(unsafe.Pointer(&b)), 64)
24+
func parseFloat(b *[]byte) (float64, error) {
25+
return strconv.ParseFloat(*(*string)(unsafe.Pointer(b)), 64)
2526
}
2627

2728
// A hack until issue golang/go#2632 is fixed.
2829
// See: https://github.com/golang/go/issues/2632
29-
func bytesToString(b []byte) string {
30-
return *(*string)(unsafe.Pointer(&b))
30+
func bytesToString(b *[]byte) string {
31+
return *(*string)(unsafe.Pointer(b))
32+
}
33+
34+
func StringToBytes(s string) []byte {
35+
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
36+
bh := reflect.SliceHeader{
37+
Data: sh.Data,
38+
Len: sh.Len,
39+
Cap: sh.Len,
40+
}
41+
return *(*[]byte)(unsafe.Pointer(&bh))
3142
}

bytes_unsafe_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func bytesEqualStrUnsafeSlower(abytes *[]byte, bstr string) bool {
2727
}
2828

2929
func TestEqual(t *testing.T) {
30-
if !equalStr([]byte{}, "") {
30+
if !equalStr(&[]byte{}, "") {
3131
t.Errorf(`equalStr("", ""): expected true, obtained false`)
3232
return
3333
}
@@ -37,11 +37,11 @@ func TestEqual(t *testing.T) {
3737
s1, s2 := longstr[:i]+"1", longstr[:i]+"2"
3838
b1 := []byte(s1)
3939

40-
if !equalStr(b1, s1) {
40+
if !equalStr(&b1, s1) {
4141
t.Errorf(`equalStr("a"*%d + "1", "a"*%d + "1"): expected true, obtained false`, i, i)
4242
break
4343
}
44-
if equalStr(b1, s2) {
44+
if equalStr(&b1, s2) {
4545
t.Errorf(`equalStr("a"*%d + "1", "a"*%d + "2"): expected false, obtained true`, i, i)
4646
break
4747
}
@@ -50,7 +50,7 @@ func TestEqual(t *testing.T) {
5050

5151
func BenchmarkEqualStr(b *testing.B) {
5252
for i := 0; i < b.N; i++ {
53-
equalStr(benchmarkBytes, benchmarkString)
53+
equalStr(&benchmarkBytes, benchmarkString)
5454
}
5555
}
5656

parser.go

Lines changed: 102 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,75 @@ func tokenEnd(data []byte) int {
3636
return len(data)
3737
}
3838

39-
func findLastTokenEnd(data []byte, token byte) int {
39+
func findTokenStart(data []byte, token byte) int {
4040
for i := len(data) - 1; i >= 0; i-- {
4141
switch data[i] {
4242
case token:
4343
return i
44+
case '[', '{':
45+
return 0
4446
}
4547
}
4648

4749
return 0
4850
}
4951

50-
func lastTokenEnd(data []byte) int {
52+
func findKeyStart(data []byte, key string) (int, error) {
53+
i := 0
54+
ln := len(data)
55+
var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
56+
57+
if ku, err := Unescape(StringToBytes(key), stackbuf[:]); err == nil {
58+
key = bytesToString(&ku)
59+
}
60+
61+
for i < ln {
62+
switch data[i] {
63+
case '"':
64+
i++
65+
keyBegin := i
66+
67+
strEnd, keyEscaped := stringEnd(data[i:])
68+
if strEnd == -1 {
69+
break
70+
}
71+
i += strEnd
72+
keyEnd := i - 1
73+
74+
valueOffset := nextToken(data[i:])
75+
if valueOffset == -1 {
76+
break
77+
}
78+
79+
i += valueOffset
80+
81+
// if string is a key, and key level match
82+
k := data[keyBegin:keyEnd]
83+
// for unescape: if there are no escape sequences, this is cheap; if there are, it is a
84+
// bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
85+
if keyEscaped {
86+
if ku, err := Unescape(k, stackbuf[:]); err != nil {
87+
break
88+
} else {
89+
k = ku
90+
}
91+
}
92+
93+
if data[i] == ':' && len(key) == len(k) && bytesToString(&k) == key {
94+
return keyBegin - 1, nil
95+
}
96+
97+
}
98+
i++
99+
}
100+
101+
return -1, KeyPathNotFoundError
102+
}
103+
104+
func tokenStart(data []byte) int {
51105
for i := len(data) - 1; i >= 0; i-- {
52106
switch data[i] {
53-
case ' ', '\n', '\r', '\t', ',', '}', ']':
107+
case '\n', '\r', '\t', ',', '{', '[':
54108
return i
55109
}
56110
}
@@ -195,7 +249,7 @@ func searchKeys(data []byte, keys ...string) int {
195249
keyUnesc = ku
196250
}
197251

198-
if equalStr(keyUnesc, keys[level-1]) {
252+
if equalStr(&keyUnesc, keys[level-1]) {
199253
keyLevel++
200254
// If we found all keys in path
201255
if keyLevel == lk {
@@ -341,9 +395,9 @@ func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]str
341395
return -1
342396
}
343397

344-
pathsBuf[level-1] = bytesToString(keyUnesc)
398+
pathsBuf[level-1] = bytesToString(&keyUnesc)
345399
for pi, p := range paths {
346-
if len(p) != level || pathFlags&bitwiseFlags[pi+1] != 0 || !equalStr(keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) {
400+
if len(p) != level || pathFlags&bitwiseFlags[pi+1] != 0 || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) {
347401
continue
348402
}
349403

@@ -544,46 +598,52 @@ func Del(data []byte, keys ...string) []byte {
544598
array = true
545599
}
546600

547-
_, _, startOffset, endOffset, err := internalGet(data, keys...)
548-
if err == nil {
549-
if !array {
550-
lastTok := lastTokenEnd(data[:startOffset])
551-
keyOffset, _ := stringEnd(data[:lastTok])
552-
lastTokEnd := tokenEnd(data[endOffset:])
553-
554-
if keyOffset == -1 {
555-
keyOffset = 0
556-
} else {
557-
keyOffset--
558-
}
559-
if lastTok != 0 {
560-
startOffset = lastTok + keyOffset
561-
}
562-
563-
if lastTokEnd >= len(data[endOffset:])-1 {
564-
lastTokEnd = 0
565-
startOffset = lastTok
566-
} else {
567-
lastTokEnd++
601+
var startOffset, keyOffset int
602+
endOffset := len(data)
603+
var err error
604+
if !array {
605+
if len(keys) > 1 {
606+
_, _, startOffset, endOffset, err = internalGet(data, keys[:lk-1]...)
607+
if err == KeyPathNotFoundError {
608+
// problem parsing the data
609+
return data
568610
}
569-
endOffset = endOffset + lastTokEnd
570-
} else {
571-
tokEnd := tokenEnd(data[endOffset:])
572-
tokStart := findLastTokenEnd(data[:startOffset], ","[0])
611+
}
573612

574-
if data[endOffset+tokEnd] == ","[0] {
575-
endOffset += tokEnd + 1
576-
} else if data[endOffset+tokEnd] == "]"[0] && data[tokStart] == ","[0] {
577-
startOffset = tokStart
578-
}
613+
keyOffset, err = findKeyStart(data[startOffset:endOffset], keys[lk-1])
614+
if err == KeyPathNotFoundError {
615+
// problem parsing the data
616+
return data
617+
}
618+
keyOffset += startOffset
619+
_, _, _, subEndOffset, _ := internalGet(data[startOffset:endOffset], keys[lk-1])
620+
endOffset = startOffset + subEndOffset
621+
tokEnd := tokenEnd(data[endOffset:])
622+
tokStart := findTokenStart(data[:keyOffset], ","[0])
623+
624+
if data[endOffset+tokEnd] == ","[0] {
625+
endOffset += tokEnd + 1
626+
} else if data[endOffset+tokEnd] == "}"[0] && data[tokStart] == ","[0] {
627+
keyOffset = tokStart
579628
}
629+
} else {
630+
_, _, keyOffset, endOffset, err = internalGet(data, keys...)
631+
if err == KeyPathNotFoundError {
632+
// problem parsing the data
633+
return data
634+
}
635+
636+
tokEnd := tokenEnd(data[endOffset:])
637+
tokStart := findTokenStart(data[:keyOffset], ","[0])
580638

581-
copy(data[startOffset:], data[endOffset:])
582-
for k, n := len(data)-endOffset+startOffset, len(data); k < n; k++ {
583-
data[k] = 0 // or the zero value of T
639+
if data[endOffset+tokEnd] == ","[0] {
640+
endOffset += tokEnd + 1
641+
} else if data[endOffset+tokEnd] == "]"[0] && data[tokStart] == ","[0] {
642+
keyOffset = tokStart
584643
}
585-
data = data[:len(data)-endOffset+startOffset]
586644
}
645+
646+
data = append(data[:keyOffset], data[endOffset:]...)
587647
return data
588648
}
589649

@@ -972,7 +1032,7 @@ func GetUnsafeString(data []byte, keys ...string) (val string, err error) {
9721032
return "", e
9731033
}
9741034

975-
return bytesToString(v), nil
1035+
return bytesToString(&v), nil
9761036
}
9771037

9781038
// GetString returns the value retrieved by `Get`, cast to a string if possible, trying to properly handle escape and utf8 symbols
@@ -1070,7 +1130,7 @@ func ParseString(b []byte) (string, error) {
10701130

10711131
// ParseNumber parses a Number ValueType into a Go float64
10721132
func ParseFloat(b []byte) (float64, error) {
1073-
if v, err := parseFloat(b); err != nil {
1133+
if v, err := parseFloat(&b); err != nil {
10741134
return 0, MalformedValueError
10751135
} else {
10761136
return v, nil

0 commit comments

Comments
 (0)