Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,21 @@ Atomic expressions:

Jsonpath operators:

+-------------------------------------+------------------------------------------------------------------------------------+
| Syntax | Meaning |
+=====================================+====================================================================================+
| *jsonpath1* ``.`` *jsonpath2* | All nodes matched by *jsonpath2* starting at any node matching *jsonpath1* |
+-------------------------------------+------------------------------------------------------------------------------------+
| *jsonpath* ``[`` *whatever* ``]`` | Same as *jsonpath*\ ``.``\ *whatever* |
+-------------------------------------+------------------------------------------------------------------------------------+
| *jsonpath1* ``..`` *jsonpath2* | All nodes matched by *jsonpath2* that descend from any node matching *jsonpath1* |
+-------------------------------------+------------------------------------------------------------------------------------+
| *jsonpath1* ``where`` *jsonpath2* | Any nodes matching *jsonpath1* with a child matching *jsonpath2* |
+-------------------------------------+------------------------------------------------------------------------------------+
| *jsonpath1* ``|`` *jsonpath2* | Any nodes matching the union of *jsonpath1* and *jsonpath2* |
+-------------------------------------+------------------------------------------------------------------------------------+
+--------------------------------------+-----------------------------------------------------------------------------------+
| Syntax | Meaning |
+======================================+===================================================================================+
| *jsonpath1* ``.`` *jsonpath2* | All nodes matched by *jsonpath2* starting at any node matching *jsonpath1* |
+--------------------------------------+-----------------------------------------------------------------------------------+
| *jsonpath* ``[`` *whatever* ``]`` | Same as *jsonpath*\ ``.``\ *whatever* |
+--------------------------------------+-----------------------------------------------------------------------------------+
| *jsonpath1* ``..`` *jsonpath2* | All nodes matched by *jsonpath2* that descend from any node matching *jsonpath1* |
+--------------------------------------+-----------------------------------------------------------------------------------+
| *jsonpath1* ``where`` *jsonpath2* | Any nodes matching *jsonpath1* with a child matching *jsonpath2* |
+--------------------------------------+-----------------------------------------------------------------------------------+
| *jsonpath1* ``wherenot`` *jsonpath2* | Any nodes matching *jsonpath1* with a child not matching *jsonpath2* |
+--------------------------------------+-----------------------------------------------------------------------------------+
| *jsonpath1* ``|`` *jsonpath2* | Any nodes matching the union of *jsonpath1* and *jsonpath2* |
+--------------------------------------+-----------------------------------------------------------------------------------+

Field specifiers ( *field* ):

Expand Down
30 changes: 30 additions & 0 deletions jsonpath_ng/jsonpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,36 @@ def __eq__(self, other):
def __hash__(self):
return hash((self.left, self.right))


class WhereNot(Where):
"""
Identical to ``Where``, but filters for only those nodes that
do *not* have a match on the right.

>>> jsonpath = WhereNot(Fields('spam'), Fields('spam'))
>>> jsonpath.find({"spam": {"spam": 1}})
[]
>>> matches = jsonpath.find({"spam": 1})
>>> matches[0].value
1

"""
def find(self, data):
return [subdata for subdata in self.left.find(data)
if not self.right.find(subdata)]

def __str__(self):
return '%s wherenot %s' % (self.left, self.right)

def __eq__(self, other):
return (isinstance(other, WhereNot)
and other.left == self.left
and other.right == self.right)

def __hash__(self):
return hash((self.left, self.right))


class Descendants(JSONPath):
"""
JSONPath that matches first the left expression then any descendant
Expand Down
5 changes: 4 additions & 1 deletion jsonpath_ng/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ def tokenize(self, string):

literals = ['*', '.', '[', ']', '(', ')', '$', ',', ':', '|', '&', '~']

reserved_words = { 'where': 'WHERE' }
reserved_words = {
'where': 'WHERE',
'wherenot': 'WHERENOT',
}

tokens = ['DOUBLEDOT', 'NUMBER', 'ID', 'NAMED_OPERATOR'] + list(reserved_words.values())

Expand Down
4 changes: 4 additions & 0 deletions jsonpath_ng/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def parse_token_stream(self, token_iterator):
('left', '|'),
('left', '&'),
('left', 'WHERE'),
('left', 'WHERENOT'),
]

def p_error(self, t):
Expand All @@ -81,6 +82,7 @@ def p_jsonpath_binop(self, p):
"""jsonpath : jsonpath '.' jsonpath
| jsonpath DOUBLEDOT jsonpath
| jsonpath WHERE jsonpath
| jsonpath WHERENOT jsonpath
| jsonpath '|' jsonpath
| jsonpath '&' jsonpath"""
op = p[2]
Expand All @@ -91,6 +93,8 @@ def p_jsonpath_binop(self, p):
p[0] = Descendants(p[1], p[3])
elif op == 'where':
p[0] = Where(p[1], p[3])
elif op == 'wherenot':
p[0] = WhereNot(p[1], p[3])
elif op == '|':
p[0] = Union(p[1], p[3])
elif op == '&':
Expand Down
10 changes: 10 additions & 0 deletions tests/test_jsonpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ def test_datumincontext_in_context_nested():
{"foo": {"bar": 3, "flag": 1}, "baz": {"bar": 2}},
),
#
# WhereNot
# --------
#
(
'(* wherenot flag) .. bar',
{'foo': {'bar': 1, 'flag': 1}, 'baz': {'bar': 2}},
4,
{'foo': {'bar': 1, 'flag': 1}, 'baz': {'bar': 4}},
),
#
# Lambdas
# -------
#
Expand Down
1 change: 1 addition & 0 deletions tests/test_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
("`this`", (("this", "NAMED_OPERATOR"),)),
("|", (("|", "|"),)),
("where", (("where", "WHERE"),)),
("wherenot", (("wherenot", "WHERENOT"),)),
)


Expand Down
3 changes: 2 additions & 1 deletion tests/test_parser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from jsonpath_ng.jsonpath import Child, Descendants, Fields, Index, Slice, Where
from jsonpath_ng.jsonpath import Child, Descendants, Fields, Index, Slice, Where, WhereNot
from jsonpath_ng.lexer import JsonPathLexer
from jsonpath_ng.parser import JsonPathParser

Expand All @@ -27,6 +27,7 @@
("foo.baz", Child(Fields("foo"), Fields("baz"))),
("foo.baz,bizzle", Child(Fields("foo"), Fields("baz", "bizzle"))),
("foo where baz", Where(Fields("foo"), Fields("baz"))),
("foo wherenot baz", WhereNot(Fields("foo"), Fields("baz"))),
("foo..baz", Descendants(Fields("foo"), Fields("baz"))),
("foo..baz.bing", Descendants(Fields("foo"), Child(Fields("baz"), Fields("bing")))),
)
Expand Down