From f40fe4ac370d7a2eec58d5ebb21801a36cf20ee3 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Wed, 31 Aug 2016 13:23:20 -0700 Subject: [PATCH] subquery syntax --- README.md | 12 ++++ gjson.go | 168 ++++++++++++++++++++++++++++++++++++++++++++++++-- gjson_test.go | 8 ++- 3 files changed, 179 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6713365..4312a96 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,10 @@ The dot and wildcard characters can be escaped with '\'. "friends.#.first" >> [ "James", "Roger" ] "friends.1.last" >> "Craig" ``` +To query an array: +``` +"friends.#[last="Murphy"].first" >> "James" +``` ## Result Type @@ -158,6 +162,14 @@ for _,name := range result.Array() { } ``` +You can also query an object inside an array: + +```go +name := gjson.Get(json, "programmers.#[lastName="Hunter"].firstName") +println(name.String()) // prints "Elliotte" +``` + + ## Simple Parse and Get There's a `Parse(json)` function that will do a simple parse, and `result.Get(path)` that will search a result. diff --git a/gjson.go b/gjson.go index 78a793a..4b73610 100644 --- a/gjson.go +++ b/gjson.go @@ -499,6 +499,12 @@ type arrayPathResult struct { alogok bool arrch bool alogkey string + query struct { + on bool + path string + op string + value string + } } func parseArrayPath(path string) (r arrayPathResult) { @@ -511,10 +517,96 @@ func parseArrayPath(path string) (r arrayPathResult) { } if path[i] == '#' { r.arrch = true - if i == 0 && len(path) > 1 && path[1] == '.' { - r.alogok = true - r.alogkey = path[2:] - r.path = path[:1] + if i == 0 && len(path) > 1 { + if path[1] == '.' { + r.alogok = true + r.alogkey = path[2:] + r.path = path[:1] + } else if path[1] == '[' { + r.query.on = true + // query + i += 2 + // whitespace + for ; i < len(path); i++ { + if path[i] > ' ' { + break + } + } + s := i + for ; i < len(path); i++ { + if path[i] <= ' ' || path[i] == '=' || + path[i] == '<' || path[i] == '>' || + path[i] == ']' { + break + } + } + r.query.path = path[s:i] + // whitespace + for ; i < len(path); i++ { + if path[i] > ' ' { + break + } + } + if i < len(path) { + s = i + if path[i] == '<' || path[i] == '>' { + if i < len(path)-1 && path[i+1] == '=' { + i++ + } + } else if path[i] == '=' { + if i < len(path)-1 && path[i+1] == '=' { + s++ + i++ + } + } + i++ + r.query.op = path[s:i] + // whitespace + for ; i < len(path); i++ { + if path[i] > ' ' { + break + } + } + s = i + for ; i < len(path); i++ { + if path[i] == '"' { + i++ + s2 := i + for ; i < len(path); i++ { + if path[i] > '\\' { + continue + } + if path[i] == '"' { + // look for an escaped slash + if path[i-1] == '\\' { + n := 0 + for j := i - 2; j > s2-1; j-- { + if path[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + break + } + } + } else if path[i] == ']' { + break + } + } + if i > len(path) { + i = len(path) + } + v := path[s:i] + for len(v) > 0 && v[len(v)-1] <= ' ' { + v = v[:len(v)-1] + } + r.query.value = v + } + } } continue } @@ -773,7 +865,60 @@ func parseObject(c *parseContext, i int, path string) (int, bool) { } return i, false } - +func queryMatches(rp *arrayPathResult, value Result) bool { + rpv := rp.query.value + if len(rpv) > 2 && rpv[0] == '"' && rpv[len(rpv)-1] == '"' { + rpv = rpv[1 : len(rpv)-1] + } + switch value.Type { + case String: + switch rp.query.op { + case "=": + return value.Str == rpv + case "<": + return value.Str < rpv + case "<=": + return value.Str <= rpv + case ">": + return value.Str > rpv + case ">=": + return value.Str >= rpv + } + case Number: + rpvn, _ := strconv.ParseFloat(rpv, 64) + switch rp.query.op { + case "=": + return value.Num == rpvn + case "<": + return value.Num < rpvn + case "<=": + return value.Num <= rpvn + case ">": + return value.Num > rpvn + case ">=": + return value.Num >= rpvn + } + case True: + switch rp.query.op { + case "=": + return rpv == "true" + case ">": + return rpv == "false" + case ">=": + return true + } + case False: + switch rp.query.op { + case "=": + return rpv == "false" + case "<": + return rpv == "true" + case "<=": + return true + } + } + return false +} func parseArray(c *parseContext, i int, path string) (int, bool) { var pmatch, vesc, ok, hit bool var val string @@ -832,7 +977,18 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { } } else { i, val = parseSquash(c.json, i) - if hit { + if rp.query.on { + res := Get(val, rp.query.path) + if queryMatches(&rp, res) { + if rp.more { + c.value = Get(val, rp.path) + } else { + c.value.Raw = val + c.value.Type = JSON + } + return i, true + } + } else if hit { if rp.alogok { break } diff --git a/gjson_test.go b/gjson_test.go index c50b551..275b383 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -126,10 +126,12 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"}, }` func TestBasic(t *testing.T) { - //fmt.Printf("%v\n", Parse(basicJSON).Get("items.3.tags.#").String()) - //return - var mtok Result + + mtok = Get(basicJSON, `loggy.programmers.#[firstName == "Brett"].email`) + if mtok.String() != "aaaa" { + t.Fatalf("expected %v, got %v", "aaaa", mtok.String()) + } mtok = Get(basicJSON, "loggy") if mtok.Type != JSON { t.Fatalf("expected %v, got %v", JSON, mtok.Type)