Skip to content

Commit a5e2973

Browse files
committed
feat: resolve issue #13 by adding support for 'in' operator and regex matching (=~)
1 parent 5e38031 commit a5e2973

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed

jsonpath/jsonpath.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class JSONPath:
5858
# operators
5959
REP_SLICE_CONTENT = re.compile(r"^(-?\d*)?:(-?\d*)?(:-?\d*)?$")
6060
REP_SELECT_CONTENT = re.compile(r"^([\w.']+)(, ?[\w.']+)+$")
61-
REP_FILTER_CONTENT = re.compile(r"@([.\[].*?)(?=<=|>=|==|!=|>|<| in| not| is)|len\(@([.\[].*?)\)")
61+
REP_FILTER_CONTENT = re.compile(r"@([.\[].*?)(?=<=|>=|==|!=|>|<| in| not| is|\s|\)|$)|len\(@([.\[].*?)\)")
6262

6363
# annotations
6464
f: list
@@ -216,7 +216,7 @@ def key_func(t, k):
216216
def _filter(self, obj, i: int, path: str, step: str):
217217
r = False
218218
try:
219-
r = self.eval_func(step, None, {"__obj": obj})
219+
r = self.eval_func(step, None, {"__obj": obj, "RegexPattern": RegexPattern})
220220
except Exception:
221221
pass
222222
if r:
@@ -293,6 +293,10 @@ def _trace(self, obj, i: int, path):
293293
if step.startswith("?(") and step.endswith(")"):
294294
step = step[2:-1]
295295
step = JSONPath.REP_FILTER_CONTENT.sub(self._gen_obj, step)
296+
297+
if "=~" in step:
298+
step = re.sub(r"=~\s*/(.*?)/", r"@ RegexPattern(r'\1')", step)
299+
296300
if isinstance(obj, dict):
297301
self._filter(obj, i + 1, path, step)
298302
self._traverse(self._filter, obj, i + 1, path, step)
@@ -332,6 +336,16 @@ def _trace(self, obj, i: int, path):
332336
return
333337

334338

339+
class RegexPattern:
340+
def __init__(self, pattern):
341+
self.pattern = pattern
342+
343+
def __rmatmul__(self, other):
344+
if isinstance(other, str):
345+
return bool(re.search(self.pattern, other))
346+
return False
347+
348+
335349
def compile(expr):
336350
return JSONPath(expr)
337351

tests/test_issues.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,32 @@ def test_sorting_with_missing_keys():
143143
data2 = [{"other": 1}, {"other": 2}]
144144
with pytest.raises(JSONPathTypeError):
145145
JSONPath("$./(val)").parse(data2)
146+
147+
148+
def test_issue_13_contains_and_regex():
149+
data = {
150+
"products": [
151+
{"name": "Apple", "tags": ["fruit", "red"]},
152+
{"name": "Banana", "tags": ["fruit", "yellow"]},
153+
{"name": "Carrot", "tags": ["vegetable", "orange"]},
154+
]
155+
}
156+
157+
# Case 1: 'in' operator (list contains)
158+
# Check if 'fruit' is in tags list
159+
expr_in_list = "$.products[?('fruit' in @.tags)].name"
160+
assert JSONPath(expr_in_list).parse(data) == ["Apple", "Banana"]
161+
162+
# Case 2: 'in' operator (string contains)
163+
# Check if 'App' is in name
164+
expr_in_str = "$.products[?('App' in @.name)].name"
165+
assert JSONPath(expr_in_str).parse(data) == ["Apple"]
166+
167+
# Case 3: Regex operator =~
168+
# Check if name starts with 'B'
169+
expr_regex = "$.products[?(@.name =~ /B.*/)].name"
170+
assert JSONPath(expr_regex).parse(data) == ["Banana"]
171+
172+
# Case 4: Case insensitive regex
173+
expr_regex_case = "$.products[?(@.name =~ /(?i)apple/)].name"
174+
assert JSONPath(expr_regex_case).parse(data) == ["Apple"]

0 commit comments

Comments
 (0)