4
4
"bytes"
5
5
"errors"
6
6
"fmt"
7
- "strconv"
8
7
)
9
8
10
9
// Errors
19
18
MalformedStringEscapeError = errors .New ("Encountered an invalid escape sequence in a string" )
20
19
)
21
20
21
+ // How much stack space to allocate for unescaping JSON strings; if a string longer
22
+ // than this needs to be escaped, it will result in a heap allocation
23
+ const unescapeStackBufSize = 64
24
+
22
25
func tokenEnd (data []byte ) int {
23
26
for i , c := range data {
24
27
switch c {
@@ -46,24 +49,32 @@ func nextToken(data []byte) int {
46
49
47
50
// Tries to find the end of string
48
51
// Support if string contains escaped quote symbols.
49
- func stringEnd (data []byte ) int {
52
+ func stringEnd (data []byte ) (int , bool ) {
53
+ escaped := false
50
54
for i , c := range data {
51
55
if c == '"' {
52
- j := i - 1
53
- for {
54
- if j < 0 || data [j ] != '\\' {
55
- return i + 1 // even number of backslashes
56
- }
57
- j --
58
- if j < 0 || data [j ] != '\\' {
59
- break // odd number of backslashes
56
+ if ! escaped {
57
+ return i + 1 , false
58
+ } else {
59
+ j := i - 1
60
+ for {
61
+ if j < 0 || data [j ] != '\\' {
62
+ return i + 1 , true // even number of backslashes
63
+ }
64
+ j --
65
+ if j < 0 || data [j ] != '\\' {
66
+ break // odd number of backslashes
67
+ }
68
+ j --
69
+
60
70
}
61
- j --
62
71
}
72
+ } else if c == '\\' {
73
+ escaped = true
63
74
}
64
75
}
65
76
66
- return - 1
77
+ return - 1 , escaped
67
78
}
68
79
69
80
// Find end of the data structure, array or object.
@@ -76,7 +87,7 @@ func blockEnd(data []byte, openSym byte, closeSym byte) int {
76
87
for i < ln {
77
88
switch data [i ] {
78
89
case '"' : // If inside string, skip it
79
- se := stringEnd (data [i + 1 :])
90
+ se , _ := stringEnd (data [i + 1 :])
80
91
if se == - 1 {
81
92
return - 1
82
93
}
@@ -104,13 +115,15 @@ func searchKeys(data []byte, keys ...string) int {
104
115
ln := len (data )
105
116
lk := len (keys )
106
117
118
+ var stackbuf [unescapeStackBufSize ]byte // stack-allocated array for allocation-free unescaping of small strings
119
+
107
120
for i < ln {
108
121
switch data [i ] {
109
122
case '"' :
110
123
i ++
111
124
keyBegin := i
112
125
113
- strEnd := stringEnd (data [i :])
126
+ strEnd , keyEscaped := stringEnd (data [i :])
114
127
if strEnd == - 1 {
115
128
return - 1
116
129
}
@@ -124,12 +137,22 @@ func searchKeys(data []byte, keys ...string) int {
124
137
125
138
i += valueOffset
126
139
127
- // if string is a Key , and key level match
128
- if data [i ] == ':' {
140
+ // if string is a key , and key level match
141
+ if data [i ] == ':' && keyLevel == level - 1 {
129
142
key := data [keyBegin :keyEnd ]
130
143
131
- if keyLevel == level - 1 && // If key nesting level match current object nested level
132
- equalStr (& key , keys [level - 1 ]) {
144
+ // for unescape: if there are no escape sequences, this is cheap; if there are, it is a
145
+ // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
146
+ var keyUnesc []byte
147
+ if ! keyEscaped {
148
+ keyUnesc = key
149
+ } else if ku , err := Unescape (key , stackbuf [:]); err != nil {
150
+ return - 1
151
+ } else {
152
+ keyUnesc = ku
153
+ }
154
+
155
+ if equalStr (& keyUnesc , keys [level - 1 ]) {
133
156
keyLevel ++
134
157
// If we found all keys in path
135
158
if keyLevel == lk {
@@ -206,7 +229,7 @@ func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset
206
229
// if string value
207
230
if data [offset ] == '"' {
208
231
dataType = String
209
- if idx := stringEnd (data [offset + 1 :]); idx != - 1 {
232
+ if idx , _ := stringEnd (data [offset + 1 :]); idx != - 1 {
210
233
endOffset += idx + 1
211
234
} else {
212
235
return []byte {}, dataType , offset , MalformedStringError
@@ -371,9 +394,10 @@ func GetString(data []byte, keys ...string) (val string, err error) {
371
394
return string (v ), nil
372
395
}
373
396
374
- s , err := strconv .Unquote (`"` + string (v ) + `"` )
397
+ var stackbuf [unescapeStackBufSize ]byte // stack-allocated array for allocation-free unescaping of small strings
398
+ out , err := Unescape (v , stackbuf [:])
375
399
376
- return s , err
400
+ return string ( out ) , err
377
401
}
378
402
379
403
// GetFloat returns the value retrieved by `Get`, cast to a float64 if possible.
0 commit comments