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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Python JSONPath Change Log

## Version 2.0.2 (unreleased)

**Fixes**

- Fixed parsing of non-standard JSONPath regular expression literals containing an escaped solidus (`/`). This affected queries using the regex operator `=~`, like `$.some[?(@.thing =~ /fo\/[a-z]/)]`, not standard `match` and `search` functions. See [#124](https://github.com/jg-rp/python-jsonpath/issues/124).

## Version 2.0.1

**Fixes**
Expand Down
13 changes: 13 additions & 0 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,19 @@ list-literal = "[" S literal *(S "," S literal) S "]"
$..products[?(@.description =~ /.*trainers/i)]
```

You can escape a solidus (`/`) with a reverse solidus (`\`).

```
$.some[?(@.thing =~ /fo\/[a-z]/)]
```

As a Python string literal, you'd need to double escape the reverse solidus or use a raw string literal.

```python
query = r"$.some[?(@.thing =~ /fo\/[a-z]/)]"
query = "$.some[?(@.thing =~ /fo\\/[a-z]/)]"
```

### Union and intersection operators

The union or concatenation operator, `|`, combines matches from two or more paths.
Expand Down
2 changes: 1 addition & 1 deletion jsonpath/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023-present James Prior <[email protected]>
#
# SPDX-License-Identifier: MIT
__version__ = "2.0.1"
__version__ = "2.0.2"
2 changes: 1 addition & 1 deletion jsonpath/lex.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def __init__(self, *, env: JSONPathEnvironment) -> None:
)

# /pattern/ or /pattern/flags
self.re_pattern = r"/(?P<G_RE>.+?)/(?P<G_RE_FLAGS>[aims]*)"
self.re_pattern = r"/(?P<G_RE>(?:(?!(?<!\\)/).)*)/(?P<G_RE_FLAGS>[aims]*)"

# func(
self.function_pattern = r"(?P<G_FUNC>[a-z][a-z_0-9]+)(?P<G_FUNC_PAREN>\()"
Expand Down
2 changes: 1 addition & 1 deletion tests/cts
Submodule cts updated 2 files
+422 −0 cts.json
+422 −0 tests/filter.json
26 changes: 25 additions & 1 deletion tests/regex_operator.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,36 @@
"tags": ["extra"]
},
{
"name": "regex literal, escaped slash",
"name": "regex literal, escaped backslash",
"selector": "$.some[?(@.thing =~ /fo\\\\[a-z]/)]",
"document": { "some": [{ "thing": "fo\\b" }] },
"result": [{ "thing": "fo\\b" }],
"result_paths": ["$['some'][0]"],
"tags": ["extra"]
},
{
"name": "regex literal, escaped slash",
"selector": "$.some[?(@.thing =~ /fo\\/[a-z]/)]",
"document": { "some": [{ "thing": "fo/b" }] },
"result": [{ "thing": "fo/b" }],
"result_paths": ["$['some'][0]"],
"tags": ["extra"]
},
{
"name": "regex literal, escaped asterisk",
"selector": "$.some[?(@.thing =~ /fo\\*[a-z]/)]",
"document": { "some": [{ "thing": "fo*b" }] },
"result": [{ "thing": "fo*b" }],
"result_paths": ["$['some'][0]"],
"tags": ["extra"]
},
{
"name": "regex literal, escaped dot",
"selector": "$.some[?(@.thing =~ /fo\\.[a-z]/)]",
"document": { "some": [{ "thing": "fo.b" }] },
"result": [{ "thing": "fo.b" }],
"result_paths": ["$['some'][0]"],
"tags": ["extra"]
}
]
}
16 changes: 16 additions & 0 deletions tests/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,19 @@ def test_issue_117() -> None:
data = {"foo": ["bar", "baz"]}
with pytest.raises(JSONPatchError):
patch.apply(data)


def test_issue_124() -> None:
query_raw = r"$[?@type =~ /studio\/material\/.*/]"
query = "$[?@type =~ /studio\\/material\\/.*/]"

data = [
{"type": "studio/material/a"},
{"type": "studio/material/b"},
{"type": "studio foo"},
]

want = [{"type": "studio/material/a"}, {"type": "studio/material/b"}]

assert findall(query, data) == want
assert findall(query_raw, data) == want