RFC 9535 has five selectors: a name (['title'] or .title), the wildcard *, an index ([-1] counts from the end), a slice ([start:end:step]) and a filter ([?expr], with @ as the current node and &&, ||, !). The descendant segment .. searches every depth.
import json
import jsonpath
books = json.load(open("data/books.json", encoding="utf-8"))
for query in ["$.books[-1].title", "$.books[::2].id", "$.books[?@.price > 20].id",
"$..books[?@.inStock == false && @.rating > 4].author"]:
print(f"{query:52} {jsonpath.findall(query, books)}")
match = next(jsonpath.finditer("$.books[?@.year == 2026].title", books))
print(match.path, "->", match.obj)Output
$.books[-1].title ['Gardens in Glass'] $.books[::2].id [1, 3, 5] $.books[?@.price > 20].id [2, 3, 6] $..books[?@.inStock == false && @.rating > 4].author ['Priya Nair'] $['books'][3]['title'] -> Small Steps to Big Summits
Filters never raise errors: comparing a string with a number, or testing a missing member, is simply false.