Skip to content

Added comma for index + step in slice #102

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 3 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
46 changes: 28 additions & 18 deletions jsonpath_ng/jsonpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,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 @@ -621,10 +621,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=Index(index), context=datum)]
return rv

def update(self, data, val):
return self._update_base(data, val, create=False)
Expand All @@ -638,31 +640,39 @@ def _update_base(self, data, val, create):
data = _create_list_key(data)
self._pad_value(data)
if hasattr(val, '__call__'):
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:
if not isinstance(val, list):
val = [val]
# allows somelist[5,1,2] = [some_value, another_value, third_value]
# skip the indices that are too high but the value will be applied to the next index
for index in self.indices:
if len(data) > index:
data[index] = val.pop(0)
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 str(list(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)]


class Slice(JSONPath):
"""
JSONPath matching a slice of an array.
Expand Down Expand Up @@ -709,7 +719,7 @@ def find(self, datum):
return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in xrange(0, len(datum.value))]
else:
return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))[self.start:self.end:self.step]]

def update(self, data, val):
for datum in self.find(data):
datum.path.update(data, val)
Expand Down
15 changes: 10 additions & 5 deletions jsonpath_ng/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,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 @@ -136,7 +136,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 @@ -165,15 +165,20 @@ 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 : '*'"
p[0] = Slice()

def p_slice(self, p): # Currently does not support `step`
"slice : maybe_int ':' maybe_int"
p[0] = Slice(start=p[1], end=p[3])
"""slice : maybe_int ':' maybe_int
| maybe_int ':' maybe_int ':' maybe_int """
p[0] = Slice(*p[1::2])

def p_maybe_int(self, p):
"""maybe_int : NUMBER
Expand Down
50 changes: 50 additions & 0 deletions tests/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
insert_val=42,
target={'foo': [{}, 42]}),

Params(string='$.foo[1,3]',
initial_data={},
insert_val=[42, 51],
target={'foo': [{}, 42, {}, 51]}),

Params(string='$.foo[0].bar',
initial_data={},
insert_val=42,
Expand All @@ -41,6 +46,12 @@
insert_val=42,
target={'foo': [{}, {'bar': 42}]}),

# Note that each field will received the full <insert_val>
Params(string='$.foo[1,3].bar',
initial_data={},
insert_val=[42, 51],
target={'foo': [{}, {'bar': [42, 51]}, {}, {'bar': [42, 51]}]}),

Params(string='$.foo[0][0]',
initial_data={},
insert_val=42,
Expand All @@ -51,6 +62,22 @@
insert_val=42,
target={'foo': [{}, [{}, 42]]}),

# But here Note that each index will received one value of <insert_val>
Params(string='$.foo[1,3][1]',
initial_data={},
insert_val=[42, 51],
target={'foo': [{}, [{}, 42], {}, [{}, 51]]}),

Params(string='$.foo[1,3][1]',
initial_data={},
insert_val=[[42, 51], [42, 51]],
target={'foo': [{}, [{}, [42, 51]], {}, [{}, [42, 51]]]}),

Params(string='$.foo[1,3][0,2]',
initial_data={},
insert_val=[42, 51, 42, 51],
target={'foo': [{}, [42, {}, 51], {}, [42, {}, 51]]}),

Params(string='foo[0]',
initial_data={},
insert_val=42,
Expand All @@ -61,6 +88,11 @@
insert_val=42,
target={'foo': [{}, 42]}),

Params(string='foo[1,3]',
initial_data={},
insert_val=[42, 51],
target={'foo': [{}, 42, {}, 51]}),

Params(string='foo',
initial_data={},
insert_val=42,
Expand All @@ -77,6 +109,11 @@
insert_val=42,
target=[{}, 42]),

Params(string='[1,3]',
initial_data=[],
insert_val=[42, 51],
target=[{}, 42, {}, 51]),

# Converts initial data to a list if necessary
Params(string='[0]',
initial_data={},
Expand All @@ -88,6 +125,11 @@
insert_val=42,
target=[{}, 42]),

Params(string='[1,3]',
initial_data={},
insert_val=[42, 51],
target=[{}, 42, {}, 51]),

Params(string='foo[?bar="baz"].qux',
initial_data={'foo': [
{'bar': 'baz'},
Expand Down Expand Up @@ -127,6 +169,14 @@ def test_update_or_create(string, initial_data, insert_val, target):
target={'foo': 42}),
# raises TypeError

# more indices than values to insert
Params(string='$.foo[1,2,3]',
initial_data={},
insert_val=[42, 51],
target={'foo': [{}, 42, 51, {}]}),
# raises IndexError


])
@pytest.mark.xfail
def test_unsupported_classes(string, initial_data, insert_val, target):
Expand Down
5 changes: 3 additions & 2 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@
Child(Descendants(Root(), Fields('book')), Slice(start=-1))),

# The first two books
# ("$..book[0,1]", # Not implemented
# Child(Descendants(Root(), Fields('book')), Slice(end=2))),
("$..book[0,1]",
Child(Descendants(Root(), Fields('book')), Index(0,1))),

("$..book[:2]",
Child(Descendants(Root(), Fields('book')), Slice(end=2))),

Expand Down
Loading