Skip to content

Commit 1e964df

Browse files
committed
Support subqueries
It's now possible to do a query like topology.instances.#(service_roles.#(=="one"))#.service_version On a JSON document such as { "topology": { "instances": [{ "service_version": "1.2.3", "service_roles": ["one", "two"] },{ "service_version": "1.2.4", "service_roles": ["three", "four"] },{ "service_version": "1.2.2", "service_roles": ["one"] }] } } Resulting in ["1.2.3","1.2.2"]
1 parent 90ca176 commit 1e964df

File tree

2 files changed

+282
-79
lines changed

2 files changed

+282
-79
lines changed

gjson.go

Lines changed: 213 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -736,106 +736,121 @@ func parseArrayPath(path string) (r arrayPathResult) {
736736
r.alogkey = path[2:]
737737
r.path = path[:1]
738738
} else if path[1] == '[' || path[1] == '(' {
739-
var end byte
740-
if path[1] == '[' {
741-
end = ']'
742-
} else {
743-
end = ')'
744-
}
745-
r.query.on = true
746739
// query
747-
i += 2
748-
// whitespace
749-
for ; i < len(path); i++ {
750-
if path[i] > ' ' {
740+
r.query.on = true
741+
if true {
742+
qpath, op, value, _, fi, ok := parseQuery(path[i:])
743+
if !ok {
744+
// bad query, end now
751745
break
752746
}
753-
}
754-
s := i
755-
for ; i < len(path); i++ {
756-
if path[i] <= ' ' ||
757-
path[i] == '!' ||
758-
path[i] == '=' ||
759-
path[i] == '<' ||
760-
path[i] == '>' ||
761-
path[i] == '%' ||
762-
path[i] == end {
763-
break
747+
r.query.path = qpath
748+
r.query.op = op
749+
r.query.value = value
750+
i = fi - 1
751+
if i+1 < len(path) && path[i+1] == '#' {
752+
r.query.all = true
764753
}
765-
}
766-
r.query.path = path[s:i]
767-
// whitespace
768-
for ; i < len(path); i++ {
769-
if path[i] > ' ' {
770-
break
754+
} else {
755+
var end byte
756+
if path[1] == '[' {
757+
end = ']'
758+
} else {
759+
end = ')'
771760
}
772-
}
773-
if i < len(path) {
774-
s = i
775-
if path[i] == '!' {
776-
if i < len(path)-1 && (path[i+1] == '=' ||
777-
path[i+1] == '%') {
778-
i++
779-
}
780-
} else if path[i] == '<' || path[i] == '>' {
781-
if i < len(path)-1 && path[i+1] == '=' {
782-
i++
761+
i += 2
762+
// whitespace
763+
for ; i < len(path); i++ {
764+
if path[i] > ' ' {
765+
break
783766
}
784-
} else if path[i] == '=' {
785-
if i < len(path)-1 && path[i+1] == '=' {
786-
s++
787-
i++
767+
}
768+
s := i
769+
for ; i < len(path); i++ {
770+
if path[i] <= ' ' ||
771+
path[i] == '!' ||
772+
path[i] == '=' ||
773+
path[i] == '<' ||
774+
path[i] == '>' ||
775+
path[i] == '%' ||
776+
path[i] == end {
777+
break
788778
}
789779
}
790-
i++
791-
r.query.op = path[s:i]
780+
r.query.path = path[s:i]
792781
// whitespace
793782
for ; i < len(path); i++ {
794783
if path[i] > ' ' {
795784
break
796785
}
797786
}
798-
s = i
799-
for ; i < len(path); i++ {
800-
if path[i] == '"' {
801-
i++
802-
s2 := i
803-
for ; i < len(path); i++ {
804-
if path[i] > '\\' {
805-
continue
806-
}
807-
if path[i] == '"' {
808-
// look for an escaped slash
809-
if path[i-1] == '\\' {
810-
n := 0
811-
for j := i - 2; j > s2-1; j-- {
812-
if path[j] != '\\' {
813-
break
787+
if i < len(path) {
788+
s = i
789+
if path[i] == '!' {
790+
if i < len(path)-1 && (path[i+1] == '=' ||
791+
path[i+1] == '%') {
792+
i++
793+
}
794+
} else if path[i] == '<' || path[i] == '>' {
795+
if i < len(path)-1 && path[i+1] == '=' {
796+
i++
797+
}
798+
} else if path[i] == '=' {
799+
if i < len(path)-1 && path[i+1] == '=' {
800+
s++
801+
i++
802+
}
803+
}
804+
i++
805+
r.query.op = path[s:i]
806+
// whitespace
807+
for ; i < len(path); i++ {
808+
if path[i] > ' ' {
809+
break
810+
}
811+
}
812+
s = i
813+
for ; i < len(path); i++ {
814+
if path[i] == '"' {
815+
i++
816+
s2 := i
817+
for ; i < len(path); i++ {
818+
if path[i] > '\\' {
819+
continue
820+
}
821+
if path[i] == '"' {
822+
// look for an escaped slash
823+
if path[i-1] == '\\' {
824+
n := 0
825+
for j := i - 2; j > s2-1; j-- {
826+
if path[j] != '\\' {
827+
break
828+
}
829+
n++
830+
}
831+
if n%2 == 0 {
832+
continue
814833
}
815-
n++
816-
}
817-
if n%2 == 0 {
818-
continue
819834
}
835+
break
820836
}
821-
break
822837
}
838+
} else if path[i] == end {
839+
if i+1 < len(path) && path[i+1] == '#' {
840+
r.query.all = true
841+
}
842+
break
823843
}
824-
} else if path[i] == end {
825-
if i+1 < len(path) && path[i+1] == '#' {
826-
r.query.all = true
827-
}
828-
break
829844
}
845+
if i > len(path) {
846+
i = len(path)
847+
}
848+
v := path[s:i]
849+
for len(v) > 0 && v[len(v)-1] <= ' ' {
850+
v = v[:len(v)-1]
851+
}
852+
r.query.value = v
830853
}
831-
if i > len(path) {
832-
i = len(path)
833-
}
834-
v := path[s:i]
835-
for len(v) > 0 && v[len(v)-1] <= ' ' {
836-
v = v[:len(v)-1]
837-
}
838-
r.query.value = v
839854
}
840855
}
841856
}
@@ -847,6 +862,115 @@ func parseArrayPath(path string) (r arrayPathResult) {
847862
return
848863
}
849864

865+
// splitQuery takes a query and splits it into three parts:
866+
// path, op, middle, and right.
867+
// So for this query:
868+
// #(first_name=="Murphy").last
869+
// Becomes
870+
// first_name # path
871+
// =="Murphy" # middle
872+
// .last # right
873+
// Or,
874+
// #(service_roles.#(=="one")).cap
875+
// Becomes
876+
// service_roles.#(=="one") # path
877+
// # middle
878+
// .cap # right
879+
func parseQuery(query string) (
880+
path, op, value, remain string, i int, ok bool,
881+
) {
882+
if len(query) < 2 || query[0] != '#' ||
883+
(query[1] != '(' && query[1] != '[') {
884+
return "", "", "", "", i, false
885+
}
886+
i = 2
887+
j := 0 // start of value part
888+
depth := 1
889+
for ; i < len(query); i++ {
890+
if depth == 1 && j == 0 {
891+
switch query[i] {
892+
case '!', '=', '<', '>', '%':
893+
// start of the value part
894+
j = i
895+
continue
896+
}
897+
}
898+
if query[i] == '\\' {
899+
i++
900+
} else if query[i] == '[' || query[i] == '(' {
901+
depth++
902+
} else if query[i] == ']' || query[i] == ')' {
903+
depth--
904+
if depth == 0 {
905+
break
906+
}
907+
} else if query[i] == '"' {
908+
// inside selector string, balance quotes
909+
i++
910+
for ; i < len(query); i++ {
911+
if query[i] == '\\' {
912+
i++
913+
} else if query[i] == '"' {
914+
break
915+
}
916+
}
917+
}
918+
}
919+
if depth > 0 {
920+
return "", "", "", "", i, false
921+
}
922+
if j > 0 {
923+
path = trim(query[2:j])
924+
value = trim(query[j:i])
925+
remain = query[i+1:]
926+
// parse the compare op from the value
927+
var opsz int
928+
switch {
929+
case len(value) == 1:
930+
opsz = 1
931+
case value[0] == '!' && value[1] == '=':
932+
opsz = 2
933+
case value[0] == '!' && value[1] == '%':
934+
opsz = 2
935+
case value[0] == '<' && value[1] == '=':
936+
opsz = 2
937+
case value[0] == '>' && value[1] == '=':
938+
opsz = 2
939+
case value[0] == '=' && value[1] == '=':
940+
value = value[1:]
941+
opsz = 1
942+
case value[0] == '<':
943+
opsz = 1
944+
case value[0] == '>':
945+
opsz = 1
946+
case value[0] == '=':
947+
opsz = 1
948+
case value[0] == '%':
949+
opsz = 1
950+
}
951+
op = value[:opsz]
952+
value = trim(value[opsz:])
953+
} else {
954+
path = trim(query[2:i])
955+
remain = query[i+1:]
956+
}
957+
return path, op, value, remain, i + 1, true
958+
}
959+
960+
func trim(s string) string {
961+
left:
962+
if len(s) > 0 && s[0] <= ' ' {
963+
s = s[1:]
964+
goto left
965+
}
966+
right:
967+
if len(s) > 0 && s[len(s)-1] <= ' ' {
968+
s = s[:len(s)-1]
969+
goto right
970+
}
971+
return s
972+
}
973+
850974
type objectPathResult struct {
851975
part string
852976
path string
@@ -1135,6 +1259,16 @@ func queryMatches(rp *arrayPathResult, value Result) bool {
11351259
if len(rpv) > 2 && rpv[0] == '"' && rpv[len(rpv)-1] == '"' {
11361260
rpv = rpv[1 : len(rpv)-1]
11371261
}
1262+
if !value.Exists() {
1263+
return false
1264+
}
1265+
if rp.query.op == "" {
1266+
// the query is only looking for existence, such as:
1267+
// friends.#(name)
1268+
// which makes sure that the array "friends" has an element of
1269+
// "name" that exists
1270+
return true
1271+
}
11381272
switch value.Type {
11391273
case String:
11401274
switch rp.query.op {

0 commit comments

Comments
 (0)