diff --git a/jsonpath_ng/ext/filter.py b/jsonpath_ng/ext/filter.py index e0bd48a..96390c9 100644 --- a/jsonpath_ng/ext/filter.py +++ b/jsonpath_ng/ext/filter.py @@ -96,6 +96,9 @@ def __init__(self, target, op, value): def find(self, datum): datum = self.target.find(DatumInContext.wrap(datum)) + if self.op == "!": + # Negated relative query existence test + return not datum if not datum: return [] if self.op is None: diff --git a/jsonpath_ng/ext/parser.py b/jsonpath_ng/ext/parser.py index 756b72c..2db603b 100644 --- a/jsonpath_ng/ext/parser.py +++ b/jsonpath_ng/ext/parser.py @@ -23,7 +23,7 @@ class ExtendedJsonPathLexer(lexer.JsonPathLexer): """Custom LALR-lexer for JsonPath""" - literals = lexer.JsonPathLexer.literals + ['?', '@', '+', '*', '/', '-'] + literals = lexer.JsonPathLexer.literals + ['?', '@', '+', '*', '/', '-', '!'] tokens = (['BOOL'] + parser.JsonPathLexer.tokens + ['FILTER_OP', 'SORT_DIRECTION', 'FLOAT']) @@ -120,6 +120,10 @@ def p_expression(self, p): __, left, op, right = p p[0] = _filter.Expression(left, op, right) + def p_expression_not(self, p): + "expression : '!' jsonpath" + p[0] = _filter.Expression(p[2], p[1], None) + def p_expressions_expression(self, p): "expressions : expression" p[0] = [p[1]] diff --git a/tests/test_jsonpath_rw_ext.py b/tests/test_jsonpath_rw_ext.py index a4876d5..d0a1244 100644 --- a/tests/test_jsonpath_rw_ext.py +++ b/tests/test_jsonpath_rw_ext.py @@ -110,6 +110,12 @@ [{"cow": "moo"}], id="filter_eq3", ), + pytest.param( + 'objects[?cow!="moo"]', + {"objects": [{"cow": "moo"}, {"cow": "neigh"}]}, + [{"cow": "neigh"}], + id="filter_ne", + ), pytest.param( "objects[?cow>5]", {"objects": [{"cow": 8}, {"cow": 7}, {"cow": 5}, {"cow": "neigh"}]}, @@ -448,6 +454,46 @@ ["green"], id="boolean-filter-string-true-string-literal", ), + pytest.param( + '$[?!@..["type"]]', + [ + { + "name": "foo", + "data": [{"value": "bar"}] + }, + { + "name": "foo", + "data": [{"value": "bar", "type": "foo"}] + } + ], + [ + { + "name": "foo", + "data": [{"value": "bar"}] + } + ], + id="negated_relative_query_existence" + ), + pytest.param( + '$[?!data[*].type]', + [ + { + "name": "foo", + "data": [{"value": "bar"}] + }, + { + "name": "foo", + "data": [{"value": "bar", "type": "foo"}] + } + ], + [ + { + "name": "foo", + "data": [{"value": "bar"}] + } + ], + id="negated_relative_query_existence_implicit_this" + ), )