Skip to content

add delete function to JsonPath #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ parser.out
/jsonpath_rw/VERSION

.idea

venv
.eggs
coverage.xml
htmlcov
8 changes: 8 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ Then:
>>> [match.value for match in jsonpath_expr.find({'foo': [{'baz': 1}, {'baz': 2}]})]
[1, 2]

# Update values
>>> jsonpath_expr.update({'foo': [{'baz': 1}, {'baz': 2}]}, 3)
{'foo': [{'baz': 3}, {'baz': 3}]}

# Exclude values
>>> jsonpath_expr.exclude({'foo': [{'baz': 1}, {'baz': 2}]})
{'foo': []}

# Matches remember where they came from
>>> [str(match.full_path) for match in jsonpath_expr.find({'foo': [{'baz': 1}, {'baz': 2}]})]
['foo.[0].baz', 'foo.[1].baz']
Expand Down
132 changes: 124 additions & 8 deletions jsonpath_rw/jsonpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ def child(self, child):
else:
return Child(self, child)

def exclude(self, data):
"""
Returns `data` without the specified path
"""
raise NotImplementedError()

def include(self, data):
"""
Returns `data` with the specified path
:param data:
:return:
"""
raise NotImplementedError()

def make_datum(self, value):
if isinstance(value, DatumInContext):
return value
Expand Down Expand Up @@ -181,6 +195,12 @@ def find(self, data):
def update(self, data, val):
return val

def exclude(self, data):
return None

def include(self, data):
return data

def __str__(self):
return '$'

Expand All @@ -201,6 +221,12 @@ def find(self, datum):
def update(self, data, val):
return val

def exclude(self, data):
return None

def include(self, data):
return data

def __str__(self):
return '`this`'

Expand Down Expand Up @@ -236,6 +262,16 @@ def update(self, data, val):
self.right.update(datum.value, val)
return data

def exclude(self, data):
for datum in self.left.find(data):
self.right.exclude(datum.value)
return data

def include(self, data):
for datum in self.left.find(data):
self.right.include(datum.value)
return data

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

Expand Down Expand Up @@ -288,6 +324,12 @@ def update(self, data, val):
datum.path.update(data, val)
return data

def exclude(self, data):
for path in reversed([datum.path for datum in self.find(data)]):
path.exclude(data)

return data

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

Expand Down Expand Up @@ -340,36 +382,45 @@ def match_recursively(datum):
for left_match in left_matches
for submatch in match_recursively(left_match)]

def is_singular():
def is_singular(self):
return False

def update(self, data, val):
def _modify(self, data, val = None, exclude = False):
# Get all left matches into a list
left_matches = self.left.find(data)
if not isinstance(left_matches, list):
left_matches = [left_matches]

def update_recursively(data):
def modify_recursively(data):
# Update only mutable values corresponding to JSON types
if not (isinstance(data, list) or isinstance(data, dict)):
return

self.right.update(data, val)
if exclude:
self.right.exclude(data)
else:
self.right.update(data, val)

# Manually do the * or [*] to avoid coercion and recurse just the right-hand pattern
if isinstance(data, list):
for i in range(0, len(data)):
update_recursively(data[i])
for i in reversed(range(0, len(data))):
modify_recursively(data[i])

elif isinstance(data, dict):
for field in data.keys():
update_recursively(data[field])
modify_recursively(data[field])

for submatch in left_matches:
update_recursively(submatch.value)
modify_recursively(submatch.value)

return data

def update(self, data, val):
return self._modify(data, val, exclude = False)

def exclude(self, data):
return self._modify(data, None, exclude = True)

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

Expand All @@ -396,6 +447,16 @@ def is_singular(self):
def find(self, data):
return self.left.find(data) + self.right.find(data)

def update(self, data, val):
self.left.update(data, val)
self.right.update(data, val)
return data

def exclude(self, data):
self.left.exclude(data)
self.right.exclude(data)
return data

class Intersect(JSONPath):
"""
JSONPath for bits that match *both* patterns.
Expand Down Expand Up @@ -462,6 +523,29 @@ def update(self, data, val):
data[field] = val
return data

def exclude(self, data):
for field in self.reified_fields(DatumInContext.wrap(data)):
if data and field in data:
del data[field]
return data

def include(self, data):
datum = DatumInContext.wrap(data)

try:
all_fields = tuple(datum.value.keys())
except AttributeError:
all_fields = ()

path_fields = self.reified_fields(datum)
remove_fields = set(all_fields) - set(path_fields)

for field in remove_fields:
if field in data:
del data[field]

return data

def __str__(self):
return ','.join(map(str, self.fields))

Expand Down Expand Up @@ -497,6 +581,20 @@ def update(self, data, val):
data[self.index] = val
return data

def exclude(self, data):
if data is not None and len(data) > self.index:
del data[self.index]
return data

def include(self, data):
if data is None:
return None

if len(data) > self.index:
return [data[self.index]]

return []

def __eq__(self, other):
return isinstance(other, Index) and self.index == other.index

Expand Down Expand Up @@ -552,6 +650,24 @@ def update(self, data, val):
datum.path.update(data, val)
return data

def exclude(self, data):
for path in reversed([datum.path for datum in self.find(data)]):
path.exclude(data)

return data

def include(self, data):

if not data:
return data

ret = []
for datum in self.find(data):
ret.append(datum.value)

data = ret
return data

def __str__(self):
if self.start == None and self.end == None and self.step == None:
return '[*]'
Expand Down
Loading