-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathmatch.go
141 lines (124 loc) · 2.93 KB
/
match.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package ujson
import (
"context"
"strings"
)
type MatchCbFunc func(paths [][]byte, value []byte) error
type MatchCallback struct {
paths []string
cb MatchCbFunc
}
type MatchOptions struct {
IgnoreCase bool
QuitIfNoCallback bool
AsyncCallback bool
}
type MatchResult struct {
Count int
}
const (
InitialPathsDepth = 32
)
// iterate the json and call matching callback for each key/value pair.
// TODO: add support for object and array
func Match(input []byte, opt *MatchOptions, res *MatchResult, cbs ...*MatchCallback) error {
ctx := context.Background()
paths := make([][]byte, 0, InitialPathsDepth)
if opt == nil {
opt = &MatchOptions{
IgnoreCase: true,
QuitIfNoCallback: true,
AsyncCallback: false,
}
}
if res == nil {
res = &MatchResult{}
}
res.Count = 0
err := Walk(input, func(level int, key, value []byte) WalkFuncRtnType {
if len(cbs) == 0 {
// do nothing if no callbacks
// also skip the object or array
if opt.QuitIfNoCallback {
return WalkRtnValQuit
}
}
// if level is equal to capacity of paths then double the size of paths
if level >= cap(paths) {
// double the size of paths
newPaths := make([][]byte, len(paths), len(paths)*2)
copy(newPaths, paths)
paths = newPaths
}
if level == 0 {
// if value to string is not { or } then return error
if value[0] != '{' && value[0] != '}' {
// skip the object or array
return WalkRtnValSkipObject
}
return WalkRtnValDefault
}
newCbs := make([]*MatchCallback, 0, len(cbs))
// set the key to the last element of paths
pathIdx := level - 1
paths = append(paths[:pathIdx], key)
// increment count
res.Count += 1
// check all callbacks
for _, cb := range cbs {
if len(cb.paths) != 0 {
// if length does not match then skip
if len(cb.paths) != level {
newCbs = append(newCbs, cb)
continue
}
// check if previous async callback failed
if opt.AsyncCallback {
select {
case <-ctx.Done():
return WalkRtnValError
default:
}
}
// if match, we call the callback but do not append to newCbs
isMatch := true
for i, p := range cb.paths {
eq := false
if opt.IgnoreCase {
eq = strings.EqualFold(p, string(paths[i]))
} else {
eq = (p == string(paths[i]))
}
if !eq {
isMatch = false
break
}
}
if !isMatch {
newCbs = append(newCbs, cb)
continue
}
if err := cb.cb(paths, value); err != nil {
return WalkRtnValError
}
} else {
// always append the match-all callback to newCbs
newCbs = append(newCbs, cb)
if opt.AsyncCallback {
go func(ctx context.Context, cb *MatchCallback) {
if err := cb.cb(paths, value); err != nil {
ctx.Done()
}
}(ctx, cb)
} else {
if err := cb.cb(paths, value); err != nil {
return WalkRtnValError
}
}
}
}
cbs = newCbs
return WalkRtnValDefault
})
return err
}