jsonpath-python
jsonpath-python is a zero-runtime-dependency JSONPath evaluator and updater implemented with a direct Python parser. It supports child selection, recursive descent, wildcards, slices, filters, regex tests, membership tests, custom sort expressions, field extraction, value updates, and path output. Its syntax extends the original Goessner-style JSONPath rather than implementing RFC 9535. That makes it unusually capable for manipulating Python dictionaries and lists, but expressions are tied to this package's dialect.
Excellent value when you own the expressions and want zero dependencies plus sorting and updates. Skip it when JSONPath portability or RFC 9535 behavior is part of the contract.
Use it if
- You want JSONPath selection and in-place updates without runtime dependencies
- You need package-specific sorting, field extraction, regex, or membership expressions in addition to ordinary selection
- You repeatedly evaluate the same expression and want a compile function plus cached search helper
- You control both expression producers and consumers, so portability to an RFC 9535 engine is not required
- You exchange JSONPath expressions with other tools or require RFC 9535 conformance: the README explicitly recommends another library for strict adherence and documents a modified dialect
- Expressions come from untrusted users: filters resemble Python expressions and include comparisons, logical operators, regex, and membership, so a narrow allowlist or a standardized engine is safer than accepting arbitrary queries
- You need a large maintainer community or long project history: the repository has about 49 stars and is primarily a compact single-project implementation
- You rely on standard quoting rules: the README says compared strings must use double quotes because single quotes have a separate special-field meaning in this dialect
- You want immutable transformations: JSONPath.update mutates matching values in the supplied object, so callers sharing that dictionary can observe changes unexpectedly
Setup reality
pip install jsonpath-python is genuinely small: version 1.1.6 supports Python 3.8 and newer and declares no runtime dependencies. The import is from jsonpath, not jsonpath_python, which is an easy first mistake. The simple search(expression, data) function uses an internal LRU cache, while compile(expression) returns a reusable JSONPath object; pick one clear application convention rather than recompiling inside tight loops. The bigger setup decision is syntax ownership. The README states that this implementation modifies and extends the original JSONPath syntax and is not the choice for strict RFC 9535 compatibility. Its sorter /(...), extractor (...), special-field quoting, and update method will not move cleanly to many other engines. Compared string literals inside filters must use double quotes because single quotes identify fields containing dots or spaces. Output defaults to values, but path mode changes the result contract and must be chosen consistently if downstream code stores or edits paths. Invalid syntax can raise ExprSyntaxError, while incompatible types can raise JSONPathTypeError; neither should be flattened into an empty match because an empty list is also a valid query result. update changes the original object and accepts a callable, so copy data first when callers expect persistence-style immutability. Finally, do not accept an unrestricted expression language at an external API boundary without limits. Recursive descent, regexes, wide filters, and sorts can consume meaningful CPU and memory on large documents even when the parser itself has no dependencies.
Patterns
Select values from every array itemselect-child-values
from jsonpath import JSONPath
data = {"books": [{"title": "A"}, {"title": "B"}]}
titles = JSONPath("$.books[*].title").parse(data)parse returns a list of matches even when only one value matches.
Find a field at any depthsearch-recursively
from jsonpath import search
prices = search("$..price", document)search uses LRU caching for repeated expressions, but recursive descent can still scan a large document.
Filter objects with multiple conditionsfilter-items
from jsonpath import JSONPath
cheap_fiction = JSONPath(
'$.books[?(@.category=="fiction" and @.price<10)].title'
).parse(data)Compared strings must use double quotes; this dialect reserves single quotes for another field-selection form.
Select items containing a tagfilter-membership
matches = JSONPath(
"$.items[?('featured' in @.tags)]"
).parse(data)This in operator is a package extension and should not be assumed portable to other JSONPath engines.
Match a field with a regular expressionfilter-regex
matching = JSONPath(
"$.books[?(@.title =~ /.*Python.*/)].title"
).parse(data)Regex evaluation over attacker-controlled expressions or very large inputs needs application-level limits.
Take a Python-style array sliceslice-array
middle = JSONPath("$.books[1:3]").parse(data)
every_other = JSONPath("$.books[::2]").parse(data)The slice follows this package's Python-like behavior; test negative bounds if expressions must move between engines.
Sort objects before selecting valuessort-results
ascending = JSONPath("$.books[/(price)].title").parse(data)
descending = JSONPath("$.books[/(~price)].title").parse(data)The /(...) sorter and ~ descending marker are jsonpath-python extensions, not RFC 9535 syntax.
Project selected fields into dictionariesextract-fields
summary = JSONPath(
"$.books[/(price)].(title,price)"
).parse(data)Field extraction with (...) is package-specific; consumers should not treat the expression as portable JSONPath.
Update every matching valueupdate-matches
from jsonpath import JSONPath
JSONPath("$.books[*].price").update(
data, lambda price: round(price * 0.9, 2)
)update mutates data in place; use copy.deepcopy first when other code shares the original object.
Distinguish bad syntax from no matcheshandle-expression-errors
from jsonpath import JSONPath, ExprSyntaxError, JSONPathTypeError
try:
values = JSONPath(expression).parse(data)
except (ExprSyntaxError, JSONPathTypeError) as exc:
raise ValueError(f"invalid JSONPath: {exc}") from excAn empty result is valid and differs from an invalid expression or an incompatible comparison type.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| jsonpath-ng | PyPI | You want a mature parser with an AST-oriented extended syntax and a larger installed ecosystem |
| jsonpath2 | PyPI | You prefer a different JSONPath implementation and can validate its dialect against your expression corpus |
| jmespath | PyPI | You can choose a standardized query language designed for projections and transformations rather than JSONPath compatibility |