Skip to content

Commit d5e9790

Browse files
committed
Add support for array idx keys
1 parent f04e003 commit d5e9790

File tree

3 files changed

+227
-70
lines changed

3 files changed

+227
-70
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,34 @@ jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, off
6060
fmt.Println(jsonparser.Get(value, "url"))
6161
}, "person", "avatars")
6262

63+
// Or use can access fields by index!
64+
jsonparser.GetInt("person", "avatars", "[0]", "url")
65+
6366
// You can use `ObjectEach` helper to iterate objects { "key1":object1, "key2":object2, .... "keyN":objectN }
6467
jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
6568
fmt.Printf("Key: '%s'\n Value: '%s'\n Type: %s\n", string(key), string(value), dataType)
6669
return nil
6770
}, "person", "name")
71+
72+
// The most efficient way to extract multiple keys is `EachKey`
73+
74+
paths := [][]string{
75+
[]string{"person", "name", "fullName"},
76+
[]string{"person", "avatars", "[0]", "url"},
77+
[]string{"company", "url"},
78+
}
79+
jsonparser.EachKey(data, func(idx int, value []byte, vt jsonparser.ValueType, err error){
80+
switch idx {
81+
case 0: // []string{"person", "name", "fullName"}
82+
...
83+
case 1: // []string{"person", "avatars", "[0]", "url"}
84+
...
85+
case 2: // []string{"company", "url"},
86+
...
87+
}
88+
}, paths...)
89+
90+
// For more information see docs below
6891
```
6992

7093
## Need to speedup your app?
@@ -93,6 +116,8 @@ Returns:
93116
Accepts multiple keys to specify path to JSON value (in case of quering nested structures).
94117
If no keys are provided it will try to extract the closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation.
95118

119+
Note that keys can be an array indexes: `jsonparser.GetInt("person", "avatars", "[0]", "url")`, pretty cool, yeah?
120+
96121
### **`GetString`**
97122
```go
98123
func GetString(data []byte, keys ...string) (val string, err error)

parser.go

Lines changed: 126 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"math"
8+
"strconv"
89
)
910

1011
// Errors
@@ -31,7 +32,7 @@ func tokenEnd(data []byte) int {
3132
}
3233
}
3334

34-
return -1
35+
return len(data)
3536
}
3637

3738
// Find position of next character which is not whitespace
@@ -116,6 +117,10 @@ func searchKeys(data []byte, keys ...string) int {
116117
ln := len(data)
117118
lk := len(keys)
118119

120+
if lk == 0 {
121+
return 0
122+
}
123+
119124
var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
120125

121126
for i < ln {
@@ -168,11 +173,34 @@ func searchKeys(data []byte, keys ...string) int {
168173
case '}':
169174
level--
170175
case '[':
171-
// Do not search for keys inside arrays
172-
if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
173-
return -1
176+
// If we want to get array element by index
177+
if keyLevel == level && keys[level][0] == '[' {
178+
aIdx, _ := strconv.Atoi(keys[level][1 : len(keys[level])-1])
179+
180+
var curIdx int
181+
var valueFound []byte
182+
var valueOffset int
183+
184+
ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
185+
if (curIdx == aIdx) {
186+
valueFound = value
187+
valueOffset = offset
188+
}
189+
curIdx += 1
190+
})
191+
192+
if valueFound == nil {
193+
return -1
194+
} else {
195+
return i + valueOffset + searchKeys(valueFound, keys[level+1:]...)
196+
}
174197
} else {
175-
i += arraySkip - 1
198+
// Do not search for keys inside arrays
199+
if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
200+
return -1
201+
} else {
202+
i += arraySkip - 1
203+
}
176204
}
177205
}
178206

@@ -191,15 +219,12 @@ func init() {
191219
}
192220

193221
func sameTree(p1, p2 []string) bool {
194-
if len(p1) == 1 && len(p2) == 1 {
195-
return true
222+
minLen := len(p1)
223+
if len(p2) < minLen {
224+
minLen = len(p2)
196225
}
197226

198-
for pi_1, p_1 := range p1[:len(p1)-1] {
199-
if len(p2)-2 < pi_1 {
200-
break
201-
}
202-
227+
for pi_1, p_1 := range p1[:minLen] {
203228
if p2[pi_1] != p_1 {
204229
return false
205230
}
@@ -209,11 +234,19 @@ func sameTree(p1, p2 []string) bool {
209234
}
210235

211236
func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int {
212-
var pathFlags, ignorePathFlags int64
237+
var pathFlags int64
213238
var level, pathsMatched, i int
214239
ln := len(data)
215240

241+
var maxPath int
242+
for _, p := range paths {
243+
if len(p) > maxPath {
244+
maxPath = len(p)
245+
}
246+
}
247+
216248
var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings
249+
pathsBuf := make([]string, maxPath)
217250

218251
for i < ln {
219252
switch data[i] {
@@ -252,58 +285,41 @@ func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]str
252285
keyUnesc = ku
253286
}
254287

255-
for pi, p := range paths {
256-
if len(p) < level || (pathFlags&bitwiseFlags[pi]) != 0 || (ignorePathFlags&bitwiseFlags[pi] != 0) {
257-
continue
258-
}
288+
if maxPath >= level {
289+
pathsBuf[level-1] = bytesToString(&keyUnesc)
290+
291+
for pi, p := range paths {
292+
if len(p) != level || pathFlags&bitwiseFlags[pi+1] != 0 || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) {
293+
continue
294+
}
259295

260-
if equalStr(&keyUnesc, p[level-1]) {
261296
match = pi
262297

263-
if len(p) == level {
264-
i++
265-
pathsMatched++
266-
pathFlags |= bitwiseFlags[pi]
298+
i++
299+
pathsMatched++
300+
pathFlags |= bitwiseFlags[pi+1]
267301

268-
v, dt, of, e := Get(data[i:])
269-
cb(pi, v, dt, e)
302+
v, dt, of, e := Get(data[i:])
303+
cb(pi, v, dt, e)
270304

271-
if of != -1 {
272-
i += of
273-
}
305+
if of != -1 {
306+
i += of
307+
}
274308

275-
if pathsMatched == len(paths) {
276-
return i
277-
}
309+
if pathsMatched == len(paths) {
310+
return i
278311
}
279312
}
280313
}
281314

282315
if match == -1 {
283-
ignorePathFlags = 0
284316
tokenOffset := nextToken(data[i+1:])
285317
i += tokenOffset
286318

287319
if data[i] == '{' {
288320
blockSkip := blockEnd(data[i:], '{', '}')
289321
i += blockSkip + 1
290322
}
291-
} else {
292-
m_p := paths[match]
293-
294-
for pi, p := range paths {
295-
if pi == match {
296-
continue
297-
}
298-
299-
if len(p) < level || (pathFlags&bitwiseFlags[pi]) != 0 || (ignorePathFlags&bitwiseFlags[pi] != 0) {
300-
continue
301-
}
302-
303-
if !sameTree(m_p, p) {
304-
ignorePathFlags |= bitwiseFlags[pi]
305-
}
306-
}
307323
}
308324

309325
switch data[i] {
@@ -318,12 +334,61 @@ func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]str
318334
case '}':
319335
level--
320336
case '[':
321-
// Do not search for keys inside arrays
322-
if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
323-
return -1
337+
var arrIdxFlags int64
338+
var pIdxFlags int64
339+
for pi, p := range paths {
340+
if len(p) < level+1 || pathFlags&bitwiseFlags[pi+1] != 0 || p[level][0] != '[' || !sameTree(p, pathsBuf[:level]) {
341+
continue
342+
}
343+
344+
aIdx, _ := strconv.Atoi(p[level][1: len(p[level]) - 1])
345+
arrIdxFlags |= bitwiseFlags[aIdx+1]
346+
pIdxFlags |= bitwiseFlags[pi+1]
347+
}
348+
349+
if arrIdxFlags > 0 {
350+
level++
351+
352+
var curIdx int
353+
arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) {
354+
if (arrIdxFlags&bitwiseFlags[curIdx+1] != 0) {
355+
for pi, p := range paths {
356+
if pIdxFlags&bitwiseFlags[pi+1] != 0 {
357+
aIdx, _ := strconv.Atoi(p[level-1][1: len(p[level-1]) - 1])
358+
359+
if curIdx == aIdx {
360+
of := searchKeys(value, p[level:]...)
361+
362+
pathsMatched++
363+
pathFlags |= bitwiseFlags[pi+1]
364+
365+
if of != -1 {
366+
v, dt, _, e := Get(value[of:])
367+
cb(pi, v, dt, e)
368+
}
369+
}
370+
}
371+
}
372+
}
373+
374+
curIdx += 1
375+
})
376+
377+
if pathsMatched == len(paths) {
378+
return i
379+
}
380+
381+
i += arrOff - 1
324382
} else {
325-
i += arraySkip - 1
383+
// Do not search for keys inside arrays
384+
if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 {
385+
return -1
386+
} else {
387+
i += arraySkip - 1
388+
}
326389
}
390+
case ']':
391+
level--
327392
}
328393

329394
i++
@@ -476,28 +541,28 @@ func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset
476541
}
477542

478543
// ArrayEach is used when iterating arrays, accepts a callback function with the same return arguments as `Get`.
479-
func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (err error) {
544+
func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error) {
480545
if len(data) == 0 {
481-
return MalformedObjectError
546+
return -1, MalformedObjectError
482547
}
483548

484-
offset := 1
549+
offset = 1
485550

486551
if len(keys) > 0 {
487552
if offset = searchKeys(data, keys...); offset == -1 {
488-
return KeyPathNotFoundError
553+
return offset, KeyPathNotFoundError
489554
}
490555

491556
// Go to closest value
492557
nO := nextToken(data[offset:])
493558
if nO == -1 {
494-
return MalformedJsonError
559+
return offset, MalformedJsonError
495560
}
496561

497562
offset += nO
498563

499564
if data[offset] != '[' {
500-
return MalformedArrayError
565+
return offset, MalformedArrayError
501566
}
502567

503568
offset++
@@ -511,7 +576,7 @@ func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int
511576
}
512577

513578
if t != NotExist {
514-
cb(v, t, o, e)
579+
cb(v, t, offset + o - len(v), e)
515580
}
516581

517582
if e != nil {
@@ -522,7 +587,7 @@ func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int
522587

523588
skipToToken := nextToken(data[offset:])
524589
if skipToToken == -1 {
525-
return MalformedArrayError
590+
return offset, MalformedArrayError
526591
}
527592
offset += skipToToken
528593

@@ -531,13 +596,13 @@ func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int
531596
}
532597

533598
if data[offset] != ',' {
534-
return MalformedArrayError
599+
return offset, MalformedArrayError
535600
}
536601

537602
offset++
538603
}
539604

540-
return nil
605+
return offset, nil
541606
}
542607

543608
// ObjectEach iterates over the key-value pairs of a JSON object, invoking a given callback for each such entry

0 commit comments

Comments
 (0)