Skip to content

Commit 2f2fff5

Browse files
committed
searchKeys now accepts escaped JSON keys
The keys parameter passed in to searchKeys are interpreted literally, however, and are not unescaped. If search key may contain escape sequences that should be unescaped, the calling code must handle this.
1 parent 14bfe18 commit 2f2fff5

File tree

2 files changed

+43
-8
lines changed

2 files changed

+43
-8
lines changed

parser.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ func searchKeys(data []byte, keys ...string) int {
116116
ln := len(data)
117117
lk := len(keys)
118118

119+
var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
120+
119121
for i < ln {
120122
switch data[i] {
121123
case '"':
@@ -138,14 +140,22 @@ func searchKeys(data []byte, keys ...string) int {
138140

139141
// if string is a Key, and key level match
140142
if data[i] == ':' {
141-
key := unsafeBytesToString(data[keyBegin:keyEnd])
142-
143-
if keyLevel == level-1 && // If key nesting level match current object nested level
144-
keys[level-1] == key {
145-
keyLevel++
146-
// If we found all keys in path
147-
if keyLevel == lk {
148-
return i + 1
143+
key := data[keyBegin:keyEnd]
144+
145+
// for unescape: if there are no escape sequences, this is cheap; if there are, it is a
146+
// bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize
147+
if keyUnesc, err := unescape(key, stackbuf[:]); err != nil {
148+
return -1
149+
} else {
150+
keyUnescStr := unsafeBytesToString(keyUnesc)
151+
152+
if keyLevel == level-1 && // If key nesting level match current object nested level
153+
keys[level-1] == keyUnescStr {
154+
keyLevel++
155+
// If we found all keys in path
156+
if keyLevel == lk {
157+
return i + 1
158+
}
149159
}
150160
}
151161
} else {

parser_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,30 @@ var getTests = []Test{
158158
isFound: true,
159159
data: `3`,
160160
},
161+
162+
// Escaped key tests
163+
Test{
164+
desc: `key with simple escape`,
165+
json: `{"a\\b":1}`,
166+
path: []string{"a\\b"},
167+
isFound: true,
168+
data: `1`,
169+
},
170+
Test{
171+
desc: `key with Unicode escape`,
172+
json: `{"a\u00B0b":1}`,
173+
path: []string{"a\u00B0b"},
174+
isFound: true,
175+
data: `1`,
176+
},
177+
Test{
178+
desc: `key with complex escape`,
179+
json: `{"a\uD83D\uDE03b":1}`,
180+
path: []string{"a\U0001F603b"},
181+
isFound: true,
182+
data: `1`,
183+
},
184+
161185
Test{ // This test returns a match instead of a parse error, as checking for the malformed JSON would reduce performance
162186
desc: `malformed with trailing whitespace`,
163187
json: `{"a":1 `,
@@ -268,6 +292,7 @@ var getTests = []Test{
268292
path: []string{"a"},
269293
isErr: true,
270294
},
295+
271296
Test{ // This test returns not found instead of a parse error, as checking for the malformed JSON would reduce performance
272297
desc: "malformed key (followed by comma followed by colon)",
273298
json: `{"a",:1}`,

0 commit comments

Comments
 (0)