Library / SDK
jsonpath-ng/jsonpath-ng avatar
jsonpath-ng/jsonpath-ng

jsonpath-ng: a standards oriented JSONPath for Python

Finally, a JSONPath implementation for Python that aims to be standard compliant. Enjoy!

737 stars136 forksPythonApache-2.0

At a glance

What is it?
jsonpath-ng is an Apache 2.0 licensed Python implementation of JSONPath that aims to be compliant with RFC 9535, and its distinguishing feature is that a JSONPath expression is a first class object that can be analysed, printed and extended, with matches that remember where they came from and can update or remove what they point at. The judgement is that this is a language implementation rather than a pattern matcher, and that the choices it makes about associativity and grouping are the ones that keep it portable.
Who is it for?
jsonpath-ng fits a Python project that treats JSONPath as a query language rather than as a string to compile, because the expression object can be inspected, printed, built in code and extended, and because the matches carry their full path so a result can be edited in place rather than by rebuilding a path string.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A language implementation rather than a pattern matcher

jsonpath-ng is a Python implementation of JSONPath that aims to be standards compliant, as defined in RFC 9535, and the readme is explicit about how it differs from other implementations.

It is a full language implementation, meaning the JSONPath expressions are first class objects, easy to analyze, transform, parse, print and extend. That single sentence is the whole difference from the common approach, where a JSONPath library is a regular expression with a different syntax and the expression is a string that only exists to be matched.

The package also merges two older projects, jsonpath-rw and jsonpath-rw-ext, and adds several AST API enhancements, most notably the ability to update or remove nodes in the tree. So the history matters if you are migrating: the syntax you may know from those libraries is here, and the operations you could not do are now available.

The project is MIT style permissive under the Apache 2.0 licence, written by Tomas Aparicio, and the manifest describes it as a final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming. The package version is 1.11.0, the last push was on 2026-10-19, and the recent releases have come quickly, 1.10.0 on 2026-10-09, 1.10.1 on 2026-10-12 and 1.11.0 on 2026-10-16.

Matches remember where they came from

The quick start in the readme is a series of examples that build on each other, and the second one is the one that changes how you use the library.

python
from jsonpath_ng import jsonpath, parse

jsonpath_expr = parse('foo[*].baz')

[match.value for match in jsonpath_expr.find({'foo': [{'baz': 1}, {'baz': 2}]})]

The comment on the parse line says it is a real parser, not just a regex, and points at the extension possibility below. The find call returns match objects rather than raw values, and each match has a value attribute.

The second example is what the AST buys you. The matches remember where they came from, and the full path of a match can be printed:

[str(match.full_path) for match in jsonpath_expr.find({'foo': [{'baz': 1}, {'baz': 2}]})] ['foo.[0].baz', 'foo.[1].baz']

So a match is not just a value, it is a value plus a location. And because the location is a first class object, you can use it to modify the tree, updating every match, updating one match, or removing the matched nodes entirely with the filter method, which takes a predicate and keeps the nodes that the predicate rejects.

That is the practical difference between a matcher and a language. With a matcher you get values and you rebuild paths by hand to modify them. With this library the path is a value you were handed.

There is one more convenience in the examples, an automatic identifier feature. Setting jsonpath.auto_id_field to id makes the library supply an id for pieces of data that do not have one, which is the sort of thing a JSON API backend wants.

The extended parser, and the operators that break associativity

The readme describes the syntax as including some additional features and omitting some problematic ones, those that make it unportable, and the reasoning is best followed.

New operators such as the pipe character and where are available, and parentheses are used for grouping rather than for callbacks into Python. The stated reason for that is that with these changes the language is not trivially associative, which is a precise and unusual thing for a README to say.

It matters because associativity is what a portable implementation has to be careful about. When an operator is not associative, the order in which the nodes are combined changes the result, and an implementation that quietly picks one order will disagree with another implementation on the same expression. Grouping gives the user a way to say which order they meant, and the library treats the parentheses as a grouping construct rather than as a place to paste a Python callable.

The other syntax note is that fields may be quoted whether or not they are contained in brackets, so a field name with a space or a dot in it can be written the same way whether or not you put it in square brackets.

The atomic expressions and the operator table are given as two tables in the readme, covering the root object, the current object, named operators, plain fields, bracketed fields and array access, and then the operators for child access, bracket access, descendant search, the where and wherenot predicates and the pipe.

The extended parser is a separate import, and the readme notes that the Node client library has the same interface as the JavaScript one.

python
from jsonpath_ng.ext import parse

jsonpath_expr = parse('foo[*].baz')

The name is a little misleading, because the extended parser accepts the standard syntax too, it is the one that understands the additions.

You can build an expression in Python instead of parsing one

The last example in the quick start is the one that justifies the whole design, and it is short.

python
from jsonpath_ng.jsonpath import Fields
from jsonpath_ng.jsonpath import Slice

jsonpath_expr_direct = Fields('foo').child(Slice('*')).child(Fields('baz'))

The comment says this is equivalent to parsing the string form, and it is, because the parsed expression is a tree of these same objects. A field is a Fields node, a wildcard is a Slice node, and child wires one to the next.

So an expression can be produced programmatically, which means a tool can build a query from parts, a form field, a user interface, a configuration file, without string concatenation and without worrying about quoting, and then print it back out in the source syntax, because the objects know how to represent themselves.

The readme also mentions a handy extension in the same area, named operators such as parent, written with backticks in the expression and resolved by the library. A path can therefore ask for a relative position, the parent of the matched node, and the example shows it reaching outside the subtree the rest of the expression selected.

Taken together, these three capabilities, parse, build and print, are what make the expression an object rather than a string, and they are the reason a metaprogramming claim in the package description is not just marketing.

The manifest tells you the floor and the audience

The project metadata is short and specific, and two details in it will decide whether you can use the library at all.

The Python requirement is 3.11 or newer, and the classifiers list 3.11 through 3.15, with the development status marked as production or stable. The licence is Apache 2.0 and the build uses setuptools with a requirement of version 77.0.3 or newer.

There is also a command line entry point, jsonpath_ng, which points at jsonpath_ng.bin.jsonpath:entry_point. A library with a console script is unusual, and the useful consequence is that an expression can be evaluated from a shell, which is exactly what you want when you are debugging a path that a colleague reports as failing.

The dependency groups are equally restrained. The test group pulls in hypothesis, pytest, pytest-codspeed for the benchmarks and pytest-randomly for the order of the tests, and the development group adds prek for the pre-commit hooks, scriv for the changelog and tox for the matrix. There is no runtime dependency to speak of, which is what you would expect from a library whose job is to parse and evaluate expressions.

The package is a src layout, with the code in src, the tests in tests, the documentation in docs and a changelog.d directory for the pending changelog fragments, which is the scriv convention.

Property based tests, random order and a 90 percent coverage gate

The test configuration says more about how the library is maintained than the release history does.

The test group includes hypothesis, so the test suite generates expressions and data rather than only asserting on a fixed list of cases, which is the right tool for a parser. It also includes pytest-randomly, which shuffles the order of the tests, so a test that passes only because another test ran first is caught. And pytest-codspeed is there for the benchmarks, so the performance of the parser is tracked.

The coverage configuration is explicit. Branch coverage is on, the source is the package and the tests, and the fail_under setting is 90, which means a release with coverage below that fails the build. The report skips covered files, and the omit list excludes the parser generator directories, which is the one part of the codebase that is not hand written.

Those excluded directories deserve a sentence, because they explain a design decision. The names point at PLY, the Python implementation of a lex and yacc parser generator, and the library carries its own copy of it. That is why the package has no runtime dependencies, and it is also why the coverage omission exists: generated tables and a generated parser are not code you review.

There is a codespell configuration as well, with a list of paths to skip that includes the parser directories and a local copy of the RFC document, and an ignore list with a single word. It is a small detail, and it is the kind of detail that suggests the repository is looked after by someone who notices.

The release process is bumpversion, scriv and twine

The Makefile is the clearest statement of the workflow, and it is a traditional one.

Versioning is handled by bumpversion, with a bump target for a patch release and a bump-minor target for a minor one, both of which commit, tag and update the version in the package's init file. The tag target creates an annotated tag and pushes it to the origin, and the history target runs git changelog against that tag.

Releasing is python -m build followed by twine upload of the distribution files, with the build and egg-info directories cleaned afterwards. The lint target runs prek across the repository, which is the pre-commit runner, and the test target runs tox, which is where the environment matrix lives. There are also targets for the API documentation with sphinx-apidoc and for building the HTML docs.

Two details in the file show the care. The PYTHONPATH is exported to point at the src directory, so the tests run against the working tree rather than an installed copy, and the install target installs in editable mode with the development group.

The repository carries the supporting pieces for that workflow: a .pre-commit-config.yaml, a tox.ini, a changelog.d directory for the fragments that scriv will collect into the changelog, a CHANGELOG.rst, a CONTRIBUTING.rst, a .devcontainer directory and a .git-blame-ignore-revs file for the reformatting commits that would otherwise pollute the blame of the repository.

Editorial conclusion

jsonpath-ng fits a Python project that treats JSONPath as a query language rather than as a string to compile, because the expression object can be inspected, printed, built in code and extended, and because the matches carry their full path so a result can be edited in place rather than by rebuilding a path string. It is the wrong choice if you want the widest possible dialect coverage, because the library deliberately omits the features that made JSONPath unportable and adds operators that other implementations do not have, so an expression written for another library may not parse here. It is also the wrong choice if you need a tiny dependency, since the package ships a parser generator inside it. Before you adopt it, read the syntax section rather than assuming another library's dialect, and check the version floor, because the current release requires Python 3.11 and the project has moved quickly through the 1.10 and 1.11 series.

Frequently asked questions

What is jsonpath-ng?

It is an Apache 2.0 licensed Python implementation of JSONPath that aims to be compliant with RFC 9535. It merges jsonpath-rw and jsonpath-rw-ext, treats expressions as first class objects that can be analysed, printed and extended, and adds the ability to update and remove matching nodes.

How do I install jsonpath-ng?

Run pip install --upgrade jsonpath-ng. The manifest requires Python 3.11 or newer, and the classifiers cover 3.11 through 3.15 with the development status marked as production or stable.

What is the difference between the standard parser and the extended one?

The extended parser comes from jsonpath_ng.ext and understands the additions, such as the pipe operator and the where and wherenot predicates, and it treats parentheses as grouping rather than as a place to call into Python. The standard parser comes from jsonpath_ng.

Can I build a JSONPath expression in Python?

Yes. The expression objects are importable, so Fields('foo').child(Slice('*')).child(Fields('baz')) builds the same expression that parsing the string form produces, and the objects can be printed back into their source syntax.

Official sources

  1. Issues
  2. jsonpath-ng/jsonpath-ng on GitHub
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jsonpath-ng-jsonpath-ng.svg)](https://hysenlabs.com/projects/jsonpath-ng-jsonpath-ng)

Was this useful?