Skip to content

Support comma separated indices in path #173

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

Merged
merged 1 commit into from
Dec 1, 2024
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
46 changes: 30 additions & 16 deletions jsonpath_ng/jsonpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,8 +643,8 @@ class Index(JSONPath):
NOTE: For the concrete syntax of `[*]`, the abstract syntax is a Slice() with no parameters (equiv to `[:]`
"""

def __init__(self, index):
self.index = index
def __init__(self, *indices):
self.indices = indices

def find(self, datum):
return self._find_base(datum, create=False)
Expand All @@ -658,10 +658,12 @@ def _find_base(self, datum, create):
if datum.value == {}:
datum.value = _create_list_key(datum.value)
self._pad_value(datum.value)
if datum.value and len(datum.value) > self.index:
return [DatumInContext(datum.value[self.index], path=self, context=datum)]
else:
return []
rv = []
for index in self.indices:
# invalid indices do not crash, return [] instead
if datum.value and len(datum.value) > index:
rv += [DatumInContext(datum.value[index], path=self, context=datum)]
return rv

def update(self, data, val):
return self._update_base(data, val, create=False)
Expand All @@ -675,28 +677,40 @@ def _update_base(self, data, val, create):
data = _create_list_key(data)
self._pad_value(data)
if hasattr(val, '__call__'):
data[self.index] = val.__call__(data[self.index], data, self.index)
elif len(data) > self.index:
data[self.index] = val
for index in self.indices:
val.__call__(data[index], data, index)
else:
for index in self.indices:
if len(data) > index:
try:
if isinstance(val, list):
# allows somelist[5,1,2] = [some_value, another_value, third_value]
data[index] = val.pop(0)
else:
data[index] = val
except Exception as e:
raise e
return data

def filter(self, fn, data):
if fn(data[self.index]):
data.pop(self.index) # relies on mutation :(
for index in self.indices:
if fn(data[index]):
data.pop(index) # relies on mutation :(
return data

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

def __str__(self):
return '[%i]' % self.index
return '[%i]' % self.indices

def __repr__(self):
return '%s(index=%r)' % (self.__class__.__name__, self.index)
return '%s(indices=%r)' % (self.__class__.__name__, self.indices)

def _pad_value(self, value):
if len(value) <= self.index:
pad = self.index - len(value) + 1
_max = max(self.indices)
if len(value) <= _max:
pad = _max - len(value) + 1
value += [{} for __ in range(pad)]

def __hash__(self):
Expand Down
10 changes: 7 additions & 3 deletions jsonpath_ng/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def p_jsonpath_root(self, p):

def p_jsonpath_idx(self, p):
"jsonpath : '[' idx ']'"
p[0] = p[2]
p[0] = Index(*p[2])

def p_jsonpath_slice(self, p):
"jsonpath : '[' slice ']'"
Expand All @@ -132,7 +132,7 @@ def p_jsonpath_child_fieldbrackets(self, p):

def p_jsonpath_child_idxbrackets(self, p):
"jsonpath : jsonpath '[' idx ']'"
p[0] = Child(p[1], p[3])
p[0] = Child(p[1], Index(*p[3]))

def p_jsonpath_child_slicebrackets(self, p):
"jsonpath : jsonpath '[' slice ']'"
Expand Down Expand Up @@ -161,7 +161,11 @@ def p_fields_comma(self, p):

def p_idx(self, p):
"idx : NUMBER"
p[0] = Index(p[1])
p[0] = [p[1]]

def p_idx_comma(self, p):
"idx : idx ',' idx "
p[0] = p[1] + p[3]

def p_slice_any(self, p):
"slice : '*'"
Expand Down
5 changes: 4 additions & 1 deletion tests/test_jsonpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ def test_datumincontext_in_context_nested():
# -------
#
("[0]", ["foo", "bar", "baz"], "test", ["test", "bar", "baz"]),
("[0, 1]", ["foo", "bar", "baz"], "test", ["test", "test", "baz"]),
("[0, 1]", ["foo", "bar", "baz"], ["test", "test 1"], ["test", "test 1", "baz"]),
#
# Slices
# ------
Expand Down Expand Up @@ -156,7 +158,8 @@ def test_datumincontext_in_context_nested():
@parsers
def test_update(parse, expression, data, update_value, expected_value):
data_copy = copy.deepcopy(data)
result = parse(expression).update(data_copy, update_value)
update_value_copy = copy.deepcopy(update_value)
result = parse(expression).update(data_copy, update_value_copy)
assert result == expected_value


Expand Down