From 1e941a433e68544f5053742a86a156f3e9da2cf8 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Thu, 11 Aug 2016 18:51:29 -0700 Subject: [PATCH] added path escaping --- gjson.go | 148 ++++++++++++++++++++++++++++++++------------------ gjson_test.go | 36 ++++++++++-- 2 files changed, 126 insertions(+), 58 deletions(-) diff --git a/gjson.go b/gjson.go index 3be93fc..e6dad09 100644 --- a/gjson.go +++ b/gjson.go @@ -121,7 +121,38 @@ func Get(json string, path string) Result { // parse the path. just split on the dot for i := 0; i < len(path); i++ { - if path[i] == '.' { + next_part: + if path[i] == '\\' { + // go into escape mode + epart := []byte(path[s:i]) + i++ + if i < len(path) { + epart = append(epart, path[i]) + i++ + for ; i < len(path); i++ { + if path[i] == '\\' { + i++ + if i < len(path) { + epart = append(epart, path[i]) + } + continue + } else if path[i] == '.' { + parts = append(parts, part{wild: wild, key: string(epart)}) + if wild { + wild = false + } + s = i + 1 + i++ + goto next_part + } else if path[i] == '*' || path[i] == '?' { + wild = true + } + epart = append(epart, path[i]) + } + } + parts = append(parts, part{wild: wild, key: string(epart)}) + goto end_parts + } else if path[i] == '.' { parts = append(parts, part{wild: wild, key: path[s:i]}) if wild { wild = false @@ -132,6 +163,7 @@ func Get(json string, path string) Result { } } parts = append(parts, part{wild: wild, key: path[s:]}) +end_parts: var i, depth int var squashed string @@ -223,33 +255,19 @@ read_key: var val string var vc byte for ; i < len(json); i++ { - switch json[i] { - case 't', 'f', 'n': // true, false, null - vc = json[i] - s = i - i++ - for ; i < len(json); i++ { - // let's pick up any character. it doesn't matter. - if json[i] < 'a' || json[i] > 'z' { - break - } - } - val = json[s:i] - goto proc_val - case '{': // open object - i++ - vc = '{' - goto proc_delim - case '[': // open array - i++ - vc = '[' - goto proc_delim - case '"': // string + if json[i] < '"' { // control character + continue + } + if json[i] < '-' { // string i++ // we read the val below vc = '"' goto proc_val - case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': // number + } + if json[i] < '[' { // number + if json[i] == ':' { + continue + } vc = '0' s = i i++ @@ -265,6 +283,28 @@ read_key: val = json[s:i] goto proc_val } + if json[i] < ']' { // '[' + i++ + vc = '[' + goto proc_delim + } + if json[i] < 'u' { // true, false, null + vc = json[i] + s = i + i++ + for ; i < len(json); i++ { + // let's pick up any character. it doesn't matter. + if json[i] < 'a' || json[i] > 'z' { + break + } + } + val = json[s:i] + goto proc_val + } + // must be an open objet + i++ + vc = '{' + goto proc_delim } // sanity check before we move on @@ -280,37 +320,39 @@ proc_delim: // the first '[' or '{' has already been read depth := 1 for ; i < len(json); i++ { - if json[i] == '{' || json[i] == '[' { - depth++ - } else if json[i] == '}' || json[i] == ']' { - depth-- - if depth == 0 { - i++ - break - } - } else if json[i] == '"' { - i++ - s2 := i - for ; i < len(json); i++ { - if json[i] == '"' { - // look for an escaped slash - if json[i-1] == '\\' { - n := 0 - for j := i - 2; j > s2-1; j-- { - if json[j] != '\\' { - break - } - n++ - } - if n%2 == 0 { - continue - } - } + if json[i] >= '"' && json[i] <= '}' { + if json[i] == '{' || json[i] == '[' { + depth++ + } else if json[i] == '}' || json[i] == ']' { + depth-- + if depth == 0 { + i++ + break + } + } else if json[i] == '"' { + i++ + s2 := i + for ; i < len(json); i++ { + if json[i] == '"' { + // look for an escaped slash + if json[i-1] == '\\' { + n := 0 + for j := i - 2; j > s2-1; j-- { + if json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + break + } + } + if i == len(json) { break } - } - if i == len(json) { - break } } } diff --git a/gjson_test.go b/gjson_test.go index 0563d7b..a44cb5e 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -61,12 +61,42 @@ func TestRandomValidStrings(t *testing.T) { } } } +func testEscapePath(t *testing.T, json, path, expect string) { + if Get(json, path).String() != expect { + t.Fatalf("expected '%v', got '%v'", expect, Get(json, path).String()) + } +} + +func TestEscapePath(t *testing.T) { + json := `{ + "test":{ + "*":"valZ", + "*v":"val0", + "keyv*":"val1", + "key*v":"val2", + "keyv?":"val3", + "key?v":"val4", + "keyv.":"val5", + "key.v":"val6", + "keyk*":{"key?":"val7"} + } + }` + + testEscapePath(t, json, "test.\\*", "valZ") + testEscapePath(t, json, "test.\\*v", "val0") + testEscapePath(t, json, "test.keyv\\*", "val1") + testEscapePath(t, json, "test.key\\*v", "val2") + testEscapePath(t, json, "test.keyv\\?", "val3") + testEscapePath(t, json, "test.key\\?v", "val4") + testEscapePath(t, json, "test.keyv\\.", "val5") + testEscapePath(t, json, "test.key\\.v", "val6") + testEscapePath(t, json, "test.keyk\\*.key\\?", "val7") +} // this json block is poorly formed on purpose. var basicJSON = `{"age":100, "name":{"here":"B\\\"R"}, "noop":{"what is a wren?":"a bird"}, "happy":true,"immortal":false, - "escaped\\\"":true, "arr":["1",2,"3",{"hello":"world"},"4",5], "vals":[1,2,3,{"sadf":sdf"asdf"}],"name":{"first":"tom","last":null}}` @@ -123,10 +153,6 @@ func TestBasic(t *testing.T) { t.Fatal("should be nil") } - if !Get(basicJSON, "escaped\\\"").Value().(bool) { - t.Fatal("could not escape") - } - Get(basicJSON, "vals.hello") }