mirror of https://github.com/k3s-io/k3s
Allow missing keys in jsonpath
It is common in constrained circumstances to prefer an empty string result from JSONPath templates for missing keys over an error. Several other implementations provide this (the canonical JS and PHP, as well as the Java implementation). This also mirrors gotemplate, which allows Options("missingkey=zero"). Added simple check and simple test case.pull/6/head
parent
1dfd6ab0c1
commit
bcea2c8a4e
|
@ -34,6 +34,8 @@ type JSONPath struct {
|
||||||
beginRange int
|
beginRange int
|
||||||
inRange int
|
inRange int
|
||||||
endRange int
|
endRange int
|
||||||
|
|
||||||
|
allowMissingKeys bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(name string) *JSONPath {
|
func New(name string) *JSONPath {
|
||||||
|
@ -45,6 +47,13 @@ func New(name string) *JSONPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllowMissingKeys allows a caller to specify whether they want an error if a field or map key
|
||||||
|
// cannot be located, or simply an empty result. The receiver is returned for chaining.
|
||||||
|
func (j *JSONPath) AllowMissingKeys(allow bool) *JSONPath {
|
||||||
|
j.allowMissingKeys = allow
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
// Parse parse the given template, return error
|
// Parse parse the given template, return error
|
||||||
func (j *JSONPath) Parse(text string) (err error) {
|
func (j *JSONPath) Parse(text string) (err error) {
|
||||||
j.parser, err = Parse(j.name, text)
|
j.parser, err = Parse(j.name, text)
|
||||||
|
@ -305,7 +314,7 @@ func (j *JSONPath) findFieldInValue(value *reflect.Value, node *FieldNode) (refl
|
||||||
return value.FieldByName(node.Value), nil
|
return value.FieldByName(node.Value), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// evalField evaluates filed of struct or key of map.
|
// evalField evaluates field of struct or key of map.
|
||||||
func (j *JSONPath) evalField(input []reflect.Value, node *FieldNode) ([]reflect.Value, error) {
|
func (j *JSONPath) evalField(input []reflect.Value, node *FieldNode) ([]reflect.Value, error) {
|
||||||
results := []reflect.Value{}
|
results := []reflect.Value{}
|
||||||
// If there's no input, there's no output
|
// If there's no input, there's no output
|
||||||
|
@ -338,6 +347,9 @@ func (j *JSONPath) evalField(input []reflect.Value, node *FieldNode) ([]reflect.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
|
if j.allowMissingKeys {
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
return results, fmt.Errorf("%s is not found", node.Value)
|
return results, fmt.Errorf("%s is not found", node.Value)
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
|
|
|
@ -33,9 +33,10 @@ type jsonpathTest struct {
|
||||||
expect string
|
expect string
|
||||||
}
|
}
|
||||||
|
|
||||||
func testJSONPath(tests []jsonpathTest, t *testing.T) {
|
func testJSONPath(tests []jsonpathTest, allowMissingKeys bool, t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
j := New(test.name)
|
j := New(test.name)
|
||||||
|
j.AllowMissingKeys(allowMissingKeys)
|
||||||
err := j.Parse(test.template)
|
err := j.Parse(test.template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("in %s, parse %s error %v", test.name, test.template, err)
|
t.Errorf("in %s, parse %s error %v", test.name, test.template, err)
|
||||||
|
@ -166,10 +167,15 @@ func TestStructInput(t *testing.T) {
|
||||||
{"recurarray", "{..Book[2]}", storeData,
|
{"recurarray", "{..Book[2]}", storeData,
|
||||||
"{Category: fiction, Author: Herman Melville, Title: Moby Dick, Price: 8.99}"},
|
"{Category: fiction, Author: Herman Melville, Title: Moby Dick, Price: 8.99}"},
|
||||||
}
|
}
|
||||||
testJSONPath(storeTests, t)
|
testJSONPath(storeTests, false, t)
|
||||||
|
|
||||||
|
missingKeyTests := []jsonpathTest{
|
||||||
|
{"nonexistent field", "{.hello}", storeData, ""},
|
||||||
|
}
|
||||||
|
testJSONPath(missingKeyTests, true, t)
|
||||||
|
|
||||||
failStoreTests := []jsonpathTest{
|
failStoreTests := []jsonpathTest{
|
||||||
{"invalid identfier", "{hello}", storeData, "unrecognized identifier hello"},
|
{"invalid identifier", "{hello}", storeData, "unrecognized identifier hello"},
|
||||||
{"nonexistent field", "{.hello}", storeData, "hello is not found"},
|
{"nonexistent field", "{.hello}", storeData, "hello is not found"},
|
||||||
{"invalid array", "{.Labels[0]}", storeData, "map[string]int is not array or slice"},
|
{"invalid array", "{.Labels[0]}", storeData, "map[string]int is not array or slice"},
|
||||||
{"invalid filter operator", "{.Book[?(@.Price<>10)]}", storeData, "unrecognized filter operator <>"},
|
{"invalid filter operator", "{.Book[?(@.Price<>10)]}", storeData, "unrecognized filter operator <>"},
|
||||||
|
@ -196,7 +202,7 @@ func TestJSONInput(t *testing.T) {
|
||||||
{"exists filter", "{[?(@.z)].id}", pointsData, "i2 i5"},
|
{"exists filter", "{[?(@.z)].id}", pointsData, "i2 i5"},
|
||||||
{"bracket key", "{[0]['id']}", pointsData, "i1"},
|
{"bracket key", "{[0]['id']}", pointsData, "i1"},
|
||||||
}
|
}
|
||||||
testJSONPath(pointsTests, t)
|
testJSONPath(pointsTests, false, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestKubernetes tests some use cases from kubernetes
|
// TestKubernetes tests some use cases from kubernetes
|
||||||
|
@ -255,7 +261,7 @@ func TestKubernetes(t *testing.T) {
|
||||||
"[127.0.0.1, map[cpu:4]] [127.0.0.2, map[cpu:8]] "},
|
"[127.0.0.1, map[cpu:4]] [127.0.0.2, map[cpu:8]] "},
|
||||||
{"user password", `{.users[?(@.name=="e2e")].user.password}`, &nodesData, "secret"},
|
{"user password", `{.users[?(@.name=="e2e")].user.password}`, &nodesData, "secret"},
|
||||||
}
|
}
|
||||||
testJSONPath(nodesTests, t)
|
testJSONPath(nodesTests, false, t)
|
||||||
|
|
||||||
randomPrintOrderTests := []jsonpathTest{
|
randomPrintOrderTests := []jsonpathTest{
|
||||||
{"recursive name", "{..name}", nodesData, `127.0.0.1 127.0.0.2 myself e2e`},
|
{"recursive name", "{..name}", nodesData, `127.0.0.1 127.0.0.2 myself e2e`},
|
||||||
|
|
Loading…
Reference in New Issue