├── .flake8
├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── pre-commit.yml
│ ├── pypi.yml
│ ├── pytest.yml
│ └── sphinx.yml
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── Makefile
├── _static
│ └── .gitkeep
├── conf.py
├── exampleusage.md
├── index.rst
├── installation.md
├── jsonpath2.expressions.rst
├── jsonpath2.nodes.rst
├── jsonpath2.parser.rst
├── jsonpath2.rst
├── jsonpath2.subscripts.rst
└── make.bat
├── jsonpath2
├── __init__.py
├── expression.py
├── expressions
│ ├── __init__.py
│ ├── operator.py
│ └── some.py
├── node.py
├── nodes
│ ├── __init__.py
│ ├── current.py
│ ├── recursivedescent.py
│ ├── root.py
│ ├── subscript.py
│ └── terminal.py
├── parser
│ ├── JSONPath.g4
│ ├── JSONPath.interp
│ ├── JSONPath.tokens
│ ├── JSONPathLexer.interp
│ ├── JSONPathLexer.py
│ ├── JSONPathLexer.tokens
│ ├── JSONPathListener.py
│ ├── JSONPathParser.py
│ └── __init__.py
├── path.py
├── subscript.py
├── subscripts
│ ├── __init__.py
│ ├── arrayindex.py
│ ├── arrayslice.py
│ ├── callable.py
│ ├── filter.py
│ ├── node.py
│ ├── objectindex.py
│ └── wildcard.py
└── tojsonpath.py
├── readthedocs.yml
├── setup.py
└── tests
├── arrayslice_subscript_test.py
├── bookstore_test.py
├── expression_test.py
├── jsonpath2_test.py
├── node_subscript_test.py
├── shortcuts_test.py
└── subscript_node_test.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | # 79 chars is too strict and we don't have 80-char terminals nowadays,
3 | # 160 chars is too much since it doesn't let us use split view efficiently:
4 | max-line-length = 120
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### jsonpath2 version
2 | [Version of jsonpath2 where you are encountering the issue]
3 |
4 | ### Platform Details
5 | [Operating system distribution and release version. Cloud provider if running in the cloud]
6 |
7 | ### Scenario:
8 | [What you are trying to achieve and you can't?]
9 |
10 | ### Steps to Reproduce:
11 | [If you are filing an issue what are the things we need to do in order to reproduce your problem? How are you using this software or any resources it includes?]
12 |
13 | ### Expected Result:
14 | [What are you expecting to happen as the consequence of above reproduction steps?]
15 |
16 | ### Actual Result:
17 | [What actually happens after the reproduction steps? Include the error output or a link to a gist if possible.]
18 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description
2 |
3 | [Describe what this change achieves]
4 |
5 | ### Issues Resolved
6 |
7 | [List any existing issues this PR resolves]
8 |
9 | ### Check List
10 |
11 | - [ ] All tests pass.
12 | - [ ] New functionality includes testing.
13 | - [ ] New functionality has been documented in the README if applicable
14 |
--------------------------------------------------------------------------------
/.github/workflows/pre-commit.yml:
--------------------------------------------------------------------------------
1 | name: Run Pre-Commit
2 |
3 | on:
4 | pull_request: {}
5 | push:
6 | branches: [master]
7 |
8 | jobs:
9 | pre-commit:
10 | runs-on: ${{ matrix.os }}
11 | strategy:
12 | matrix:
13 | os: [ubuntu-latest]
14 | python-version: ['3.8', '3.9']
15 | steps:
16 | - uses: actions/checkout@v2
17 | - uses: actions/setup-python@v3
18 | with:
19 | python-version: ${{ matrix.python-version }}
20 | - uses: pre-commit/action@v2.0.3
21 |
--------------------------------------------------------------------------------
/.github/workflows/pypi.yml:
--------------------------------------------------------------------------------
1 | name: Publish to PyPi
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | tags:
8 | - v*
9 |
10 | jobs:
11 | deploy:
12 | runs-on: ${{ matrix.os }}
13 | strategy:
14 | matrix:
15 | os: [ubuntu-latest]
16 | python-version: ['3.10']
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Set up Python
20 | uses: actions/setup-python@v3
21 | with:
22 | python-version: ${{ matrix.python-version }}
23 | - name: Display Python version
24 | run: |
25 | python --version
26 | pip --version
27 | pip install coverage pep257 pre-commit pylint pytest readthedocs-sphinx-ext recommonmark setuptools sphinx sphinx-rtd-theme wheel
28 | pip install .
29 | python setup.py bdist_wheel
30 | python setup.py sdist
31 | - name: Publish package
32 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
33 | uses: pypa/gh-action-pypi-publish@release/v1
34 | with:
35 | user: __token__
36 | password: ${{ secrets.PYPI_API_TOKEN }}
37 |
--------------------------------------------------------------------------------
/.github/workflows/pytest.yml:
--------------------------------------------------------------------------------
1 | name: Run PyTest
2 |
3 | on:
4 | pull_request: {}
5 | push:
6 | branches: [master]
7 |
8 | jobs:
9 | build:
10 | runs-on: ${{ matrix.os }}
11 | strategy:
12 | matrix:
13 | os: [ubuntu-latest, macos-latest, windows-latest]
14 | python-version: ['3.8', '3.9', '3.10']
15 | exclude:
16 | - os: macos-latest
17 | python-version: '3.8'
18 | steps:
19 | - uses: actions/checkout@v3
20 | - name: Set up Python
21 | uses: actions/setup-python@v3
22 | with:
23 | python-version: ${{ matrix.python-version }}
24 | - name: Display Python version
25 | run: |
26 | python --version
27 | pip --version
28 | pip install coverage pep257 pre-commit pylint pytest readthedocs-sphinx-ext recommonmark setuptools sphinx sphinx-rtd-theme wheel
29 | pip install .
30 | cd tests
31 | coverage run --include='*/site-packages/jsonpath2/*' --omit='*/site-packages/jsonpath2/parser/JSONPath*' -m pytest -xv
32 | coverage report -m --fail-under 100
33 | cd ..
34 | python setup.py bdist_wheel
35 | python setup.py sdist
36 |
--------------------------------------------------------------------------------
/.github/workflows/sphinx.yml:
--------------------------------------------------------------------------------
1 | name: Run Sphinx Test
2 |
3 | on:
4 | pull_request: {}
5 | push:
6 | branches: [master]
7 |
8 | jobs:
9 | build:
10 | runs-on: ${{ matrix.os }}
11 | strategy:
12 | matrix:
13 | os: [ubuntu-latest, macos-latest, windows-latest]
14 | python-version: ['3.8', '3.9', '3.10',]
15 | exclude:
16 | - os: macos-latest
17 | python-version: '3.8'
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 | - name: Set up Python
22 | uses: actions/setup-python@v3
23 | with:
24 | python-version: ${{ matrix.python-version }}
25 | - name: Display Python version
26 | run: |
27 | python --version
28 | pip --version
29 | pip install coverage pep257 pre-commit pylint pytest readthedocs-sphinx-ext recommonmark setuptools sphinx sphinx-rtd-theme docutils==0.16
30 | pip install .
31 | cd docs
32 | sphinx-build -T -E -b readthedocs -d _build/doctrees-readthedocs -D language=en . _build/html
33 | sphinx-build -T -b readthedocssinglehtmllocalmedia -d _build/doctrees-readthedocssinglehtmllocalmedia -D language=en . _build/localmedia
34 | sphinx-build -b latex -D language=en -d _build/doctrees . _build/latex
35 | sphinx-build -T -b epub -d _build/doctrees-epub -D language=en . _build/epub
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | exclude: 'jsonpath2/parser/JSONPath.*'
2 | repos:
3 | - repo: https://github.com/pre-commit/pre-commit-hooks
4 | rev: v2.5.0
5 | hooks:
6 | - id: fix-encoding-pragma
7 | - id: trailing-whitespace
8 | - id: check-merge-conflict
9 | - id: end-of-file-fixer
10 | - id: debug-statements
11 | - id: check-added-large-files
12 | - id: check-ast
13 | - id: check-byte-order-marker
14 | - id: check-case-conflict
15 | - id: check-docstring-first
16 | - id: check-executables-have-shebangs
17 | types: [python]
18 | - id: check-json
19 | - id: check-vcs-permalinks
20 | - id: mixed-line-ending
21 | - id: name-tests-test
22 | - id: pretty-format-json
23 | args:
24 | - --autofix
25 | - --top-keys=_id
26 | - id: sort-simple-yaml
27 | files: '.yaml$'
28 | - id: check-symlinks
29 | - id: check-yaml
30 | - id: detect-private-key
31 | - id: trailing-whitespace
32 | - repo: https://github.com/psf/black
33 | rev: 22.3.0
34 | hooks:
35 | - id: black
36 | - repo: https://github.com/Lucas-C/pre-commit-hooks
37 | rev: v1.1.13
38 | hooks:
39 | - id: remove-tabs
40 | - id: remove-crlf
41 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 |
9 | ## [0.4.5] - 2022-05-05
10 | ### Changed
11 | - Fixed #45 lock ANTLR runtime to 4.10 by [@markborkum](https://github.com/markborkum)
12 |
13 | ## [0.4.4] - 2020-05-04
14 | ### Changed
15 | - Pull #48 Cleanup Requirements by [@dmlb2000](https://github.com/dmlb2000)
16 | - Fixed #46 Resolve pip>20 Compatability by [@chrisBLIT](https://github.com/chrisBLIT)
17 |
18 | ## [0.4.3] - 2020-01-23
19 | ### Changed
20 | - Fixed #42 lock ANTLR runtime to 4.7.2 by [@markborkum](https://github.com/markborkum)
21 |
22 | ## [0.4.2] - 2020-01-07
23 | ### Changed
24 | - Fixed #35 fix array slice parse by [@markborkum](https://github.com/markborkum)
25 | - Fixed #40 test Python 3.8 and use bionic by [@dmlb2000](https://github.com/dmlb2000)
26 |
27 | ## [0.4.1] - 2019-07-29
28 | ### Changed
29 | - Fixed #31 fix array slice end overrun by [@dmlb2000](https://github.com/dmlb2000)
30 |
31 | ## [0.4.0] - 2019-06-11
32 | ### Added
33 | - Initial JSONPath implementation for Python by [@markborkum](https://github.com/markborkum)
34 |
35 | ### Changed
36 | - Fixes #24, #25 added compatibility method by [@dangra](https://github.com/dangra)
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jsonpath2
2 | [](https://travis-ci.org/pacifica/python-jsonpath2)
3 |
4 | This repository contains an implementation of
5 | [JSONPath](http://goessner.net/articles/JsonPath/)
6 | ([XPath](https://www.w3.org/TR/xpath/all/) for
7 | [JSON](https://www.json.org/)) for the Python programming
8 | language.
9 |
10 | ## Documentation
11 |
12 | For installation, configuration and usage documentation please
13 | refer to the [Read the Docs](https://jsonpath2.readthedocs.io)
14 | documentation.
15 |
16 | * [Installation](docs/installation.md) documentation.
17 | * [Examples](docs/exampleusage.md) documentation.
18 |
19 |
20 | ## Contributions
21 |
22 | Contributions are accepted on GitHub via the fork and pull request workflow.
23 | GitHub has a good [help article](https://help.github.com/articles/using-pull-requests/)
24 | if you are unfamiliar with this method of contributing.
25 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SOURCEDIR = .
8 | BUILDDIR = _build
9 |
10 | # Put it first so that "make" without argument is like "make help".
11 | help:
12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
13 |
14 | .PHONY: help Makefile
15 |
16 | # Catch-all target: route all unknown targets to Sphinx using the new
17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18 | %: Makefile
19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
20 |
--------------------------------------------------------------------------------
/docs/_static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pacifica/python-jsonpath2/f7fff8e0a595ff9e7b5c6236d75ff0995a90cc98/docs/_static/.gitkeep
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """This is an autogenerated sphinx config module."""
4 | # pylint: disable=invalid-name
5 | # pylint: disable=redefined-builtin
6 | #
7 | # Configuration file for the Sphinx documentation builder.
8 | #
9 | # This file does only contain a selection of the most common options. For a
10 | # full list see the documentation:
11 | # http://www.sphinx-doc.org/en/master/config
12 |
13 | # -- Path setup --------------------------------------------------------------
14 |
15 | # If extensions (or modules to document with autodoc) are in another directory,
16 | # add these directories to sys.path here. If the directory is relative to the
17 | # documentation root, use os.path.abspath to make it absolute, like shown here.
18 | #
19 | import sys
20 | import os
21 | from recommonmark.parser import CommonMarkParser
22 | from recommonmark.transform import AutoStructify
23 |
24 | sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "tests"))
25 |
26 | # -- Project information -----------------------------------------------------
27 |
28 | project = "JSONPath2 Library"
29 | copyright = "2019, David Brown"
30 | author = "David Brown"
31 |
32 | # The short X.Y version
33 | version = ""
34 | # The full version, including alpha/beta/rc tags
35 | release = ""
36 |
37 |
38 | # -- General configuration ---------------------------------------------------
39 |
40 | # If your documentation needs a minimal Sphinx version, state it here.
41 | #
42 | # needs_sphinx = '1.0'
43 |
44 | # Add any Sphinx extension module names here, as strings. They can be
45 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
46 | # ones.
47 | extensions = [
48 | "sphinx.ext.autodoc",
49 | "sphinx.ext.viewcode",
50 | "readthedocs_ext.readthedocs",
51 | ]
52 |
53 | # Add any paths that contain templates here, relative to this directory.
54 | templates_path = ["_templates"]
55 |
56 | # The suffix(es) of source filenames.
57 | # You can specify multiple suffix as a list of string:
58 |
59 | source_suffix = {
60 | ".rst": "restructuredtext",
61 | ".md": "markdown",
62 | }
63 |
64 | # The master toctree document.
65 | master_doc = "index"
66 |
67 | # The language for content autogenerated by Sphinx. Refer to documentation
68 | # for a list of supported languages.
69 | #
70 | # This is also used if you do content translation via gettext catalogs.
71 | # Usually you set "language" from the command line for these cases.
72 | language = None
73 |
74 | # List of patterns, relative to source directory, that match files and
75 | # directories to ignore when looking for source files.
76 | # This pattern also affects html_static_path and html_extra_path.
77 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
78 |
79 | # The name of the Pygments (syntax highlighting) style to use.
80 | pygments_style = "sphinx"
81 |
82 |
83 | # -- Options for HTML output -------------------------------------------------
84 |
85 | # The theme to use for HTML and HTML Help pages. See the documentation for
86 | # a list of builtin themes.
87 | #
88 | html_theme = "sphinx_rtd_theme"
89 |
90 | # Theme options are theme-specific and customize the look and feel of a theme
91 | # further. For a list of options available for each theme, see the
92 | # documentation.
93 | #
94 | # html_theme_options = {}
95 |
96 | # Add any paths that contain custom static files (such as style sheets) here,
97 | # relative to this directory. They are copied after the builtin static files,
98 | # so a file named "default.css" will overwrite the builtin "default.css".
99 | html_static_path = ["_static"]
100 |
101 | # Custom sidebar templates, must be a dictionary that maps document names
102 | # to template names.
103 | #
104 | # The default sidebars (for documents that don't match any pattern) are
105 | # defined by theme itself. Builtin themes are using these templates by
106 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
107 | # 'searchbox.html']``.
108 | #
109 | # html_sidebars = {}
110 |
111 |
112 | # -- Options for HTMLHelp output ---------------------------------------------
113 |
114 | # Output file base name for HTML help builder.
115 | htmlhelp_basename = "JSONPath2doc"
116 |
117 |
118 | # -- Options for LaTeX output ------------------------------------------------
119 |
120 | latex_elements = {
121 | # The paper size ('letterpaper' or 'a4paper').
122 | #
123 | # 'papersize': 'letterpaper',
124 | # The font size ('10pt', '11pt' or '12pt').
125 | #
126 | # 'pointsize': '10pt',
127 | # Additional stuff for the LaTeX preamble.
128 | #
129 | # 'preamble': '',
130 | # Latex figure (float) alignment
131 | #
132 | # 'figure_align': 'htbp',
133 | }
134 |
135 | # Grouping the document tree into LaTeX files. List of tuples
136 | # (source start file, target name, title,
137 | # author, documentclass [howto, manual, or own class]).
138 | latex_documents = [
139 | (
140 | master_doc,
141 | "JSONPath2.tex",
142 | "Python JSONPath2 Documentation",
143 | "David Brown",
144 | "manual",
145 | ),
146 | ]
147 |
148 |
149 | # -- Options for manual page output ------------------------------------------
150 |
151 | # One entry per manual page. List of tuples
152 | # (source start file, name, description, authors, manual section).
153 | man_pages = [(master_doc, "jsonpath2", "Python JSONPath2 Documentation", [author], 1)]
154 |
155 |
156 | # -- Options for Texinfo output ----------------------------------------------
157 |
158 | # Grouping the document tree into Texinfo files. List of tuples
159 | # (source start file, target name, title, author,
160 | # dir menu entry, description, category)
161 | texinfo_documents = [
162 | (
163 | master_doc,
164 | "JSONPath2",
165 | "Python JSONPath2 Documentation",
166 | author,
167 | "JSONPath2",
168 | "Python implementation for JSONPath expressions.",
169 | "Miscellaneous",
170 | ),
171 | ]
172 |
173 |
174 | # -- Options for Epub output -------------------------------------------------
175 |
176 | # Bibliographic Dublin Core info.
177 | epub_title = project
178 |
179 | # The unique identifier of the text. This can be a ISBN number
180 | # or the project homepage.
181 | #
182 | # epub_identifier = ''
183 |
184 | # A unique identification for the text.
185 | #
186 | # epub_uid = ''
187 |
188 | # A list of files that should not be packed into the epub file.
189 | epub_exclude_files = ["search.html"]
190 |
191 |
192 | # -- Extension configuration -------------------------------------------------
193 | autodoc_default_options = {
194 | "member-order": "bysource",
195 | "special-members": "__init__",
196 | "undoc-members": None,
197 | "exclude-members": "__weakref__",
198 | }
199 |
200 | # app setup hook
201 |
202 |
203 | def setup(app):
204 | """Setup the hooks for recommonmark."""
205 | app.add_config_value(
206 | "recommonmark_config",
207 | {
208 | # 'url_resolver': lambda url: github_doc_root + url,
209 | "auto_toc_tree_section": "Contents",
210 | "enable_eval_rst": True,
211 | },
212 | True,
213 | )
214 | app.add_source_parser(CommonMarkParser)
215 | app.add_transform(AutoStructify)
216 |
--------------------------------------------------------------------------------
/docs/exampleusage.md:
--------------------------------------------------------------------------------
1 | # Example Usage
2 |
3 | The JSONPath2 library has several APIs available to perform JSONPath
4 | matching.
5 |
6 | ## Syntax
7 |
8 | ```eval_rst
9 | +--------+----------------------+-----------------------------------+
10 | | XPath | JSONPath | Description |
11 | +========+======================+===================================+
12 | | ``/`` | ``$`` | the root JSON value |
13 | +--------+----------------------+-----------------------------------+
14 | | ``.`` | ``@`` | the current JSON value |
15 | +--------+----------------------+-----------------------------------+
16 | | ``/`` | ``.`` or ``[]`` | child operator |
17 | +--------+----------------------+-----------------------------------+
18 | | ``//`` | ``..`` | recursive descent (depth-first |
19 | | | | search) |
20 | +--------+----------------------+-----------------------------------+
21 | | ``*`` | ``*`` | wildcard (all elements of a JSON |
22 | | | | array; all values of a JSON |
23 | | | | object; otherwise none) |
24 | +--------+----------------------+-----------------------------------+
25 | | ``[]`` | ``[]`` | subscript operator |
26 | +--------+----------------------+-----------------------------------+
27 | | ``|`` | ``[,]`` | union operator (for two or more |
28 | | | | subscript operators) |
29 | +--------+----------------------+-----------------------------------+
30 | | n/a | ``[start:end:step]`` | slice operator (subset of |
31 | | | | elements of a JSON array) |
32 | +--------+----------------------+-----------------------------------+
33 | | ``[]`` | ``?()`` | filter expression (for use with |
34 | | | | subscript operator) |
35 | +--------+----------------------+-----------------------------------+
36 |
37 | +-------------------+-----------------------------------------------+
38 | | JSONPath Filter | Description |
39 | | Expression | |
40 | +===================+===============================================+
41 | | ``$`` or ``@`` | nested JSONPath (returns ``true`` if any |
42 | | | match exists; otherwise, returns ``false``) |
43 | +-------------------+-----------------------------------------------+
44 | | ``=``, ``!=``, | binary operator, where left- and right-hand |
45 | | ``>=``, ``<=`` | operands are nested JSONPaths or JSON values |
46 | | ``>``, ``<`` | (returns ``true`` if any match exists; |
47 | | | otherwise, returns ``false``) |
48 | +-------------------+-----------------------------------------------+
49 | | ``and``, ``or``, | Boolean operator, where operands are JSONPath |
50 | | ``not`` | filter expressions |
51 | +-------------------+-----------------------------------------------+
52 | | ``contains`` | Checks if a string contains the specified |
53 | | | substring (case-sensitive), or an array |
54 | | | contains the specified element. |
55 | +-------------------+-----------------------------------------------+
56 | | ``(`` ... ``)`` | parentheses |
57 | +-------------------+-----------------------------------------------+
58 | ```
59 |
60 | ## Functions
61 |
62 | > See [#14](https://github.com/pacifica/python-jsonpath2/pull/14) for more information.
63 |
64 | The syntax for a function call is the name of the function followed by the
65 | arguments in parentheses, i.e., `name(arg1, arg2, ..., argN)`, where the
66 | arguments are either JSONPaths or JSON values.
67 |
68 | ```python
69 | >>> s = '{"hello":"Hello, world!"}'
70 | '{"hello":"Hello, world!"}'
71 | >>> import json
72 | >>> d = json.loads(s)
73 | {'hello':'Hello, world!'}
74 | >>> from jsonpath2.path import Path
75 | >>> p = Path.parse_str('$["hello"][length()]')
76 |
77 | >>> [m.current_value for m in p.match(d)]
78 | [13]
79 | >>> [m.node.tojsonpath() for m in p.match(d)]
80 | ['$["hello"][length()]']
81 | ```
82 |
83 | ```eval_rst
84 | +------------------------------+--------------------------------------+
85 | | JavaScript Function | Signature |
86 | +==============================+======================================+
87 | | Array.length_ | ``length(): int`` |
88 | +------------------------------+--------------------------------------+
89 | | Array.prototype.entries_ | ``entries(): List[Tuple[int, Any]]`` |
90 | +------------------------------+--------------------------------------+
91 | | Array.prototype.keys_ | ``keys(): List[int]`` |
92 | +------------------------------+--------------------------------------+
93 | | Array.prototype.values_ | ``values(): List[Any]`` |
94 | +------------------------------+--------------------------------------+
95 | | Object.entries_ | ``entries(): List[Tuple[str, Any]]`` |
96 | +------------------------------+--------------------------------------+
97 | | Object.keys_ | ``keys(): List[str]`` |
98 | +------------------------------+--------------------------------------+
99 | | Object.values_ | ``values(): List[Any]`` |
100 | +------------------------------+--------------------------------------+
101 | | String.length_ | ``length(): int`` |
102 | +------------------------------+--------------------------------------+
103 | | String.prototype.charAt_ | ``charAt(index: int): str`` |
104 | +------------------------------+--------------------------------------+
105 | | String.prototype.substring_ | ``substring(indexStart: int, |
106 | | | indexEnd: Optional[int]): str`` |
107 | +------------------------------+--------------------------------------+
108 |
109 | .. _Array.length: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length
110 | .. _Array.prototype.entries: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries
111 | .. _Array.prototype.keys: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/keys
112 | .. _Array.prototype.values: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
113 | .. _Object.entries: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
114 | .. _Object.keys: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
115 | .. _Object.values: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
116 | .. _String.length: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length
117 | .. _String.prototype.charAt: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt
118 | .. _String.prototype.substring: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring
119 | ```
120 |
198 |
199 | In the above table, the type aliases (`Any`, `List`, `Optional` and `Tuple`) are defined by the
200 | [`typing`](https://docs.python.org/3/library/typing.html) module from the Python Standard Library.
201 |
202 | ## Examples
203 |
204 | Some of the examples are provided by the test suite while some have been contributed via issues.
205 |
206 | ### Test Suite Examples
207 |
208 | ```eval_rst
209 | .. automodule:: bookstore_test
210 | :members:
211 | :private-members:
212 | :special-members:
213 | ```
214 |
215 | ### Issue Examples
216 |
217 | #### Issue #19
218 |
219 | This issue involved finding the full path to the matched attribute.
220 |
221 | The result isn't strictly supported by the library but code examples are provided.
222 |
223 | ```python
224 | import json
225 | import typing
226 |
227 | from jsonpath2.node import Node
228 | from jsonpath2.nodes.root import RootNode
229 | from jsonpath2.nodes.subscript import SubscriptNode
230 | from jsonpath2.nodes.terminal import TerminalNode
231 | from jsonpath2.path import Path
232 | from jsonpath2.subscript import Subscript
233 |
234 | data = json.loads("""
235 | {
236 | "values": [
237 | {"type": 1, "value": 2},
238 | {"type": 2, "value": 3},
239 | {"type": 1, "value": 10}
240 | ]
241 | }
242 | """)
243 |
244 | path = Path.parse_str("$.values.*[?(@.type = 1)].value")
245 |
246 | def get_subscripts(node: Node) -> typing.List[typing.List[Subscript]]:
247 | return get_subscripts_(node, [])
248 |
249 | def get_subscripts_(node: Node, accumulator: typing.List[typing.List[Subscript]]) -> typing.List[typing.List[Subscript]]:
250 | if isinstance(node, RootNode):
251 | return get_subscripts_(node.next_node, accumulator)
252 | elif isinstance(node, SubscriptNode):
253 | accumulator.append(node.subscripts)
254 | return get_subscripts_(node.next_node, accumulator)
255 | elif isinstance(node, TerminalNode):
256 | return accumulator
257 |
258 | for match_data in path.match(data):
259 | print(f"Value: {match_data.current_value}")
260 | print(f"JSONPath: {match_data.node.tojsonpath()}")
261 | print(f"Subscripts: {get_subscripts(match_data.node)}")
262 | print("")
263 | ```
264 |
265 | The snippet above iterates over the match results, prints the value and
266 | JSONPath and then prints the list of subscripts. The list of subscripts
267 | is constructed by traversing the structure of the abstract syntax tree
268 | for the JSONPath.
269 |
270 | The results [modulo the memory addresses] are:
271 |
272 | ```
273 | Value: 2
274 | JSONPath: $["values"][0]["value"]
275 | Subscripts: [[], [], []]
276 |
277 | Value: 10
278 | JSONPath: $["values"][2]["value"]
279 | Subscripts: [[], [], []]
280 | ```
281 |
282 | The first subscript is the `"values"` key. The second subscript is the
283 | index of the `{"type":"value"}` object. The third subscript is the
284 | `"value"` key.
285 |
286 | Note that the result (the list of subscripts) is a list of lists. This
287 | is because instances of the `SubscriptNode` class are constructed using
288 | zero or more instances of the `Subscript` class.
289 |
290 | ## Grammar and parser
291 |
292 | The [ANTLR v4](https://github.com/antlr/antlr4) grammar for JSONPath is available at `jsonpath2/parser/JSONPath.g4`.
293 |
294 | ### Installing ANTLR v4
295 |
296 | Adapted from [antlr docs](https://github.com/antlr/antlr4/blob/master/doc/getting-started.md).
297 |
298 | ```bash
299 | cd /usr/local/lib
300 | curl -O https://www.antlr.org/download/antlr-4.10.1-complete.jar
301 |
302 | export CLASSPATH=".:/usr/local/lib/antlr-4.10.1-complete.jar:$CLASSPATH"
303 |
304 | alias antlr4='java -Xmx500M -cp "/usr/local/lib/antlr-4.10.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool'
305 | alias grun='java org.antlr.v4.gui.TestRig'
306 | ```
307 |
308 | ### Building the parser for the grammar
309 |
310 | Adapted from [antlr docs](https://github.com/antlr/antlr4/blob/master/doc/python-target.md).
311 |
312 | ```bash
313 | antlr4 -Dlanguage=Python3 -o . -lib . jsonpath2/parser/JSONPath.g4
314 | ```
315 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. Pacifica Policy documentation master file, created by
2 | sphinx-quickstart on Thu Dec 6 20:05:08 2018.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to JSONPath2 documentation!
7 | =============================================
8 |
9 |
10 | This repository contains an implementation of
11 | `JSONPath `_
12 | `XPath `_ for
13 | `JSON `_ for the Python
14 | programming language.
15 |
16 | .. toctree::
17 | :maxdepth: 2
18 | :caption: Contents:
19 |
20 | installation
21 | exampleusage
22 | jsonpath2
23 |
24 | Indices and tables
25 | ==================
26 |
27 | * :ref:`genindex`
28 | * :ref:`modindex`
29 | * :ref:`search`
30 |
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | The JSONPath2 library is available through PyPi so creating a virtual
4 | environment to install is what is shown below. Please keep in mind
5 | compatibility with the Pacifica Core services.
6 |
7 | ## Installation in Virtual Environment
8 |
9 | These installation instructions are intended to work on both Windows,
10 | Linux, and Mac platforms. Please keep that in mind when following the
11 | instructions.
12 |
13 | Please install the appropriate tested version of Python for maximum
14 | chance of success.
15 |
16 | ### Linux and Mac Installation
17 |
18 | ```
19 | mkdir ~/.virtualenvs
20 | python -m virtualenv ~/.virtualenvs/pacifica
21 | . ~/.virtualenvs/pacifica/bin/activate
22 | pip install jsonpath2
23 | ```
24 |
25 | ### Windows Installation
26 |
27 | This is done using PowerShell. Please do not use Batch Command.
28 |
29 | ```
30 | mkdir "$Env:LOCALAPPDATA\virtualenvs"
31 | python.exe -m virtualenv "$Env:LOCALAPPDATA\virtualenvs\pacifica"
32 | & "$Env:LOCALAPPDATA\virtualenvs\pacifica\Scripts\activate.ps1"
33 | pip install jsonpath2
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/jsonpath2.expressions.rst:
--------------------------------------------------------------------------------
1 | Python JSONPath2 Expressions Module
2 | =============================================
3 |
4 | .. automodule:: jsonpath2.expressions
5 | :members:
6 | :private-members:
7 | :special-members:
8 | .. automodule:: jsonpath2.expressions.operator
9 | :members:
10 | :private-members:
11 | :special-members:
12 | .. automodule:: jsonpath2.expressions.some
13 | :members:
14 | :private-members:
15 | :special-members:
16 |
--------------------------------------------------------------------------------
/docs/jsonpath2.nodes.rst:
--------------------------------------------------------------------------------
1 | Python JSONPath2 Nodes Module
2 | =============================================
3 |
4 | .. automodule:: jsonpath2.nodes
5 | :members:
6 | :private-members:
7 | :special-members:
8 | .. automodule:: jsonpath2.nodes.current
9 | :members:
10 | :private-members:
11 | :special-members:
12 | .. automodule:: jsonpath2.nodes.recursivedescent
13 | :members:
14 | :private-members:
15 | :special-members:
16 | .. automodule:: jsonpath2.nodes.root
17 | :members:
18 | :private-members:
19 | :special-members:
20 | .. automodule:: jsonpath2.nodes.subscript
21 | :members:
22 | :private-members:
23 | :special-members:
24 | .. automodule:: jsonpath2.nodes.terminal
25 | :members:
26 | :private-members:
27 | :special-members:
28 |
--------------------------------------------------------------------------------
/docs/jsonpath2.parser.rst:
--------------------------------------------------------------------------------
1 | Python JSONPath2 Parser Module
2 | =============================================
3 |
4 | .. automodule:: jsonpath2.parser
5 | :members:
6 | :private-members:
7 | :special-members:
8 |
--------------------------------------------------------------------------------
/docs/jsonpath2.rst:
--------------------------------------------------------------------------------
1 | Python JSONPath2 Module
2 | =============================================
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 | :caption: Contents:
7 |
8 | jsonpath2.expressions
9 | jsonpath2.nodes
10 | jsonpath2.parser
11 | jsonpath2.subscripts
12 |
13 | .. automodule:: jsonpath2
14 | :members:
15 | :private-members:
16 | :special-members:
17 | .. automodule:: jsonpath2.expression
18 | :members:
19 | :private-members:
20 | :special-members:
21 | .. automodule:: jsonpath2.node
22 | :members:
23 | :private-members:
24 | :special-members:
25 | .. automodule:: jsonpath2.path
26 | :members:
27 | :private-members:
28 | :special-members:
29 | .. automodule:: jsonpath2.subscript
30 | :members:
31 | :private-members:
32 | :special-members:
33 | .. automodule:: jsonpath2.tojsonpath
34 | :members:
35 | :private-members:
36 | :special-members:
37 |
--------------------------------------------------------------------------------
/docs/jsonpath2.subscripts.rst:
--------------------------------------------------------------------------------
1 | Python JSONPath2 Subscripts Module
2 | =============================================
3 |
4 | .. automodule:: jsonpath2.subscripts
5 | :members:
6 | :private-members:
7 | :special-members:
8 | .. automodule:: jsonpath2.subscripts.arrayindex
9 | :members:
10 | :private-members:
11 | :special-members:
12 | .. automodule:: jsonpath2.subscripts.arrayslice
13 | :members:
14 | :private-members:
15 | :special-members:
16 | .. automodule:: jsonpath2.subscripts.callable
17 | :members:
18 | :private-members:
19 | :special-members:
20 | .. automodule:: jsonpath2.subscripts.filter
21 | :members:
22 | :private-members:
23 | :special-members:
24 | .. automodule:: jsonpath2.subscripts.node
25 | :members:
26 | :private-members:
27 | :special-members:
28 | .. automodule:: jsonpath2.subscripts.objectindex
29 | :members:
30 | :private-members:
31 | :special-members:
32 | .. automodule:: jsonpath2.subscripts.wildcard
33 | :members:
34 | :private-members:
35 | :special-members:
36 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/jsonpath2/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """The jsonpath2 module."""
4 |
5 | from typing import Generator
6 | from .path import Path
7 | from .node import MatchData
8 |
9 |
10 | def match(path_str: str, root_value: object) -> Generator[MatchData, None, None]:
11 | """
12 | Match root value of the path.
13 |
14 | The ``jsonpath2.match`` function is a shortcut to match a given JSON data
15 | structure against a JSONPath string.
16 |
17 | .. code-block:: python
18 |
19 | >>> import jsonpath2
20 | >>> doc = {'hello': 'Hello, world!'}
21 | >>> [x.current_value for x in jsonpath2.match('$.hello', doc)]
22 | ['Hello, world!']
23 | """
24 | path = Path.parse_str(path_str)
25 | return path.match(root_value)
26 |
--------------------------------------------------------------------------------
/jsonpath2/expression.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Expression module."""
4 | from abc import abstractmethod
5 | from jsonpath2.tojsonpath import ToJSONPath
6 |
7 |
8 | class Expression(ToJSONPath):
9 | """Add the expression methods to the jsonpath object."""
10 |
11 | def __eq__(self, other: object) -> bool:
12 | """Test self the same as the other object."""
13 | return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__)
14 |
15 | @abstractmethod
16 | def evaluate(
17 | self, root_value: object, current_value: object
18 | ) -> bool: # pragma: no cover abstract method
19 | """Abstract method to evaluate the expression."""
20 | raise NotImplementedError()
21 |
--------------------------------------------------------------------------------
/jsonpath2/expressions/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Expressions used in jsonpath module."""
4 |
--------------------------------------------------------------------------------
/jsonpath2/expressions/operator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """The operator expression module."""
4 | import json
5 | from typing import Callable, Generator, List, Union
6 | from jsonpath2.expression import Expression
7 | from jsonpath2.node import Node
8 |
9 |
10 | class OperatorExpression(Expression):
11 | """Basic operator expression object."""
12 |
13 | def __jsonpath__(
14 | self,
15 | ) -> Generator[str, None, None]: # pragma: no cover abstract method
16 | """Abstract method to return the jsonpath."""
17 |
18 | def evaluate(
19 | self, root_value: object, current_value: object
20 | ) -> bool: # pragma: no cover abstract method
21 | """Abstract method to evaluate the expression."""
22 |
23 |
24 | class BinaryOperatorExpression(OperatorExpression):
25 | """Binary operator expression."""
26 |
27 | def __init__(
28 | self,
29 | token: str,
30 | callback: Callable[[object, object], bool],
31 | left_node_or_value: Union[Node, object],
32 | right_node_or_value: Union[Node, object],
33 | ):
34 | """Constructor save the left right and token."""
35 | super(BinaryOperatorExpression, self).__init__()
36 | self.token = token
37 | self.callback = callback
38 | self.left_node_or_value = left_node_or_value
39 | self.right_node_or_value = right_node_or_value
40 |
41 | def __jsonpath__(self) -> Generator[str, None, None]:
42 | """Return the string json path of this expression."""
43 | if isinstance(self.left_node_or_value, Node):
44 | for left_node_token in self.left_node_or_value.__jsonpath__():
45 | yield left_node_token
46 | else:
47 | yield json.dumps(self.left_node_or_value)
48 |
49 | yield " "
50 | yield self.token
51 | yield " "
52 |
53 | if isinstance(self.right_node_or_value, Node):
54 | for right_node_token in self.right_node_or_value.__jsonpath__():
55 | yield right_node_token
56 | else:
57 | yield json.dumps(self.right_node_or_value)
58 |
59 | def evaluate(self, root_value: object, current_value: object) -> bool:
60 | """Evaluate the left and right values given the token."""
61 | if isinstance(self.left_node_or_value, Node):
62 | left_values = (
63 | left_node_match_data.current_value
64 | for left_node_match_data in self.left_node_or_value.match(
65 | root_value, current_value
66 | )
67 | )
68 | else:
69 | left_values = [
70 | self.left_node_or_value,
71 | ]
72 |
73 | if isinstance(self.right_node_or_value, Node):
74 | right_values = (
75 | right_node_match_data.current_value
76 | for right_node_match_data in self.right_node_or_value.match(
77 | root_value, current_value
78 | )
79 | )
80 | else:
81 | right_values = [
82 | self.right_node_or_value,
83 | ]
84 |
85 | return any(
86 | self.callback(left_value, right_value)
87 | for left_value in left_values
88 | for right_value in right_values
89 | )
90 |
91 |
92 | class EqualBinaryOperatorExpression(BinaryOperatorExpression):
93 | """Binary Equal operator expression."""
94 |
95 | def __init__(self, *args, **kwargs):
96 | """Constructor with the right function."""
97 | super(EqualBinaryOperatorExpression, self).__init__(
98 | "=", EqualBinaryOperatorExpression.__evaluate__, *args, **kwargs
99 | )
100 |
101 | @staticmethod
102 | def __evaluate__(x_obj, y_obj):
103 | """Perform an equal on int or float."""
104 | return x_obj == y_obj
105 |
106 |
107 | class NotEqualBinaryOperatorExpression(BinaryOperatorExpression):
108 | """Binary Equal operator expression."""
109 |
110 | def __init__(self, *args, **kwargs):
111 | """Constructor with the right function."""
112 | super(NotEqualBinaryOperatorExpression, self).__init__(
113 | "!=", NotEqualBinaryOperatorExpression.__evaluate__, *args, **kwargs
114 | )
115 |
116 | @staticmethod
117 | def __evaluate__(x_obj, y_obj):
118 | """Perform a not equal on int or float."""
119 | return x_obj != y_obj
120 |
121 |
122 | class LessThanBinaryOperatorExpression(BinaryOperatorExpression):
123 | """Expression to handle less than."""
124 |
125 | def __init__(self, *args, **kwargs):
126 | """Construct the binary operator with appropriate method."""
127 | super(LessThanBinaryOperatorExpression, self).__init__(
128 | "<", LessThanBinaryOperatorExpression.__evaluate__, *args, **kwargs
129 | )
130 |
131 | @staticmethod
132 | def __evaluate__(x_obj, y_obj):
133 | """Perform a less than on int or float."""
134 | if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)):
135 | return x_obj < y_obj
136 | return False
137 |
138 |
139 | class LessThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression):
140 | """Expression to handle less than or equal."""
141 |
142 | def __init__(self, *args, **kwargs):
143 | """Construct the binary operator with appropriate method."""
144 | super(LessThanOrEqualToBinaryOperatorExpression, self).__init__(
145 | "<=",
146 | LessThanOrEqualToBinaryOperatorExpression.__evaluate__,
147 | *args,
148 | **kwargs
149 | )
150 |
151 | @staticmethod
152 | def __evaluate__(x_obj, y_obj):
153 | """Perform a less than or equal to on int or float."""
154 | if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)):
155 | return x_obj <= y_obj
156 | return False
157 |
158 |
159 | class GreaterThanBinaryOperatorExpression(BinaryOperatorExpression):
160 | """Expression to handle greater than."""
161 |
162 | def __init__(self, *args, **kwargs):
163 | """Construct the binary operator with appropriate method."""
164 | super(GreaterThanBinaryOperatorExpression, self).__init__(
165 | ">", GreaterThanBinaryOperatorExpression.__evaluate__, *args, **kwargs
166 | )
167 |
168 | @staticmethod
169 | def __evaluate__(x_obj, y_obj):
170 | """Perform a greater than on int or float."""
171 | if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)):
172 | return x_obj > y_obj
173 | return False
174 |
175 |
176 | class GreaterThanOrEqualToBinaryOperatorExpression(BinaryOperatorExpression):
177 | """Expression to handle greater than or equal."""
178 |
179 | def __init__(self, *args, **kwargs):
180 | """Construct the binary operator with appropriate method."""
181 | super(GreaterThanOrEqualToBinaryOperatorExpression, self).__init__(
182 | ">=",
183 | GreaterThanOrEqualToBinaryOperatorExpression.__evaluate__,
184 | *args,
185 | **kwargs
186 | )
187 |
188 | @staticmethod
189 | def __evaluate__(x_obj, y_obj):
190 | """Perform a greater than or equal to on int or float."""
191 | if isinstance(x_obj, (float, int)) and isinstance(y_obj, (float, int)):
192 | return x_obj >= y_obj
193 | return False
194 |
195 |
196 | class ContainsBinaryOperatorExpression(BinaryOperatorExpression):
197 | """Expression to handle in."""
198 |
199 | def __init__(self, *args, **kwargs):
200 | """Construct the binary operator with appropriate method."""
201 | super(ContainsBinaryOperatorExpression, self).__init__(
202 | "contains", ContainsBinaryOperatorExpression.__evaluate__, *args, **kwargs
203 | )
204 |
205 | @staticmethod
206 | def __evaluate__(x_obj, y_obj):
207 | """Perform an in."""
208 | return y_obj in x_obj
209 |
210 |
211 | class UnaryOperatorExpression(OperatorExpression):
212 | """Unary operator expression base class."""
213 |
214 | def __init__(
215 | self, token: str, callback: Callable[[bool], bool], expression: Expression
216 | ):
217 | """Save the callback operator the token and expression."""
218 | super(UnaryOperatorExpression, self).__init__()
219 | self.token = token
220 | self.callback = callback
221 | self.expression = expression
222 |
223 | def __jsonpath__(self) -> Generator[str, None, None]:
224 | """Generate the jsonpath for a unary operator."""
225 | yield self.token
226 | yield " "
227 | if isinstance(
228 | self.expression, (UnaryOperatorExpression, VariadicOperatorExpression)
229 | ):
230 | yield "("
231 | for expression_token in self.expression.__jsonpath__():
232 | yield expression_token
233 | if isinstance(
234 | self.expression, (UnaryOperatorExpression, VariadicOperatorExpression)
235 | ):
236 | yield ")"
237 |
238 | def evaluate(self, root_value: object, current_value: object) -> bool:
239 | """Evaluate the unary expression."""
240 | return self.callback(self.expression.evaluate(root_value, current_value))
241 |
242 |
243 | class NotUnaryOperatorExpression(UnaryOperatorExpression):
244 | """Unary class to handle the 'not' expression."""
245 |
246 | def __init__(self, *args, **kwargs):
247 | """Call the unary operator expression with the right method."""
248 | super(NotUnaryOperatorExpression, self).__init__(
249 | "not", NotUnaryOperatorExpression.__evaluate__, *args, **kwargs
250 | )
251 |
252 | @staticmethod
253 | def __evaluate__(x_obj):
254 | """The unary not function."""
255 | return not x_obj
256 |
257 |
258 | class VariadicOperatorExpression(OperatorExpression):
259 | """Base class to handle boolean expressions of variadic type."""
260 |
261 | def __init__(
262 | self,
263 | token: str,
264 | callback: Callable[[List[bool]], bool],
265 | expressions: List[Expression] = None,
266 | ):
267 | """Save the operator token, callback and the list of expressions."""
268 | super(VariadicOperatorExpression, self).__init__()
269 | self.token = token
270 | self.callback = callback
271 | self.expressions = expressions if expressions else []
272 |
273 | def __jsonpath__(self) -> Generator[str, None, None]:
274 | """Yield the string of the expression."""
275 | expressions_count = len(self.expressions)
276 | if expressions_count == 0:
277 | pass
278 | elif expressions_count == 1:
279 | for expression_token in self.expressions[0].__jsonpath__():
280 | yield expression_token
281 | else:
282 | for expression_index, expression in enumerate(self.expressions):
283 | if expression_index > 0:
284 | yield " "
285 | yield self.token
286 | yield " "
287 |
288 | if isinstance(expression, VariadicOperatorExpression):
289 | yield "("
290 |
291 | for expression_token in expression.__jsonpath__():
292 | yield expression_token
293 |
294 | if isinstance(expression, VariadicOperatorExpression):
295 | yield ")"
296 |
297 | def evaluate(self, root_value: object, current_value: object) -> bool:
298 | """Evaluate the expressions against the boolean callback."""
299 | return self.callback(
300 | map(
301 | lambda expression: expression.evaluate(root_value, current_value),
302 | self.expressions,
303 | )
304 | )
305 |
306 |
307 | class AndVariadicOperatorExpression(VariadicOperatorExpression):
308 | """The boolean 'and' operator expression."""
309 |
310 | def __init__(self, *args, **kwargs):
311 | """Call the super with the 'and' boolean method."""
312 | super(AndVariadicOperatorExpression, self).__init__("and", all, *args, **kwargs)
313 |
314 |
315 | class OrVariadicOperatorExpression(VariadicOperatorExpression):
316 | """The boolean 'or' operator expression."""
317 |
318 | def __init__(self, *args, **kwargs):
319 | """Call the super with the 'or' boolean method."""
320 | super(OrVariadicOperatorExpression, self).__init__("or", any, *args, **kwargs)
321 |
--------------------------------------------------------------------------------
/jsonpath2/expressions/some.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Some expression module."""
4 | import json
5 | from typing import Generator, Union
6 | from jsonpath2.expression import Expression
7 | from jsonpath2.node import Node
8 |
9 |
10 | class SomeExpression(Expression):
11 | """The some expression class."""
12 |
13 | def __init__(self, next_node_or_value: Union[Node, object]):
14 | """Save the next node."""
15 | super(SomeExpression, self).__init__()
16 | self.next_node_or_value = next_node_or_value
17 |
18 | def __jsonpath__(self) -> Generator[str, None, None]:
19 | """Return the next nodes jsonpath."""
20 | if isinstance(self.next_node_or_value, Node):
21 | return self.next_node_or_value.__jsonpath__()
22 | return [json.dumps(self.next_node_or_value)]
23 |
24 | def evaluate(self, root_value: object, current_value: object) -> bool:
25 | """Evaluate the next node."""
26 | if isinstance(self.next_node_or_value, Node):
27 | for _next_node_match_data in self.next_node_or_value.match(
28 | root_value, current_value
29 | ):
30 | return True
31 | return False
32 | return bool(self.next_node_or_value)
33 |
--------------------------------------------------------------------------------
/jsonpath2/node.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """The parse tree node module."""
4 | from abc import abstractmethod
5 | from typing import Generator
6 | from jsonpath2.tojsonpath import ToJSONPath
7 |
8 |
9 | # pylint: disable=too-few-public-methods
10 | class MatchData:
11 | """
12 | Match data object for storing node values.
13 |
14 | The ``jsonpath2.node.MatchData`` class represents the JSON
15 | value and context for a JSONPath match.
16 |
17 | This class is constructed with respect to a root JSON value,
18 | a current JSON value, and an abstract syntax tree node.
19 |
20 | Attributes:
21 | - ``root_value`` The root JSON value.
22 | - ``current_value`` The current JSON value (i.e., the matching JSON value).
23 | - ``node`` The abstract syntax tree node.
24 | """
25 |
26 | def __init__(self, node, root_value, current_value):
27 | """Constructor to save root and current node values."""
28 | super(MatchData, self).__init__()
29 | self.node = node
30 | self.root_value = root_value
31 | self.current_value = current_value
32 |
33 | def __eq__(self, other: object) -> bool:
34 | """
35 | Test if two MatchData objects are the same.
36 |
37 | Tests if two instances are equal.
38 | """
39 | return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__)
40 |
41 |
42 | # pylint: enable=too-few-public-methods
43 |
44 |
45 | class Node(ToJSONPath):
46 | """
47 | Node object for the jsonpath parsetree.
48 |
49 | The ``jsonpath2.node.Node`` class represents the abstract syntax tree for a JSONPath.
50 | """
51 |
52 | def __eq__(self, other: object) -> bool:
53 | """
54 | Determine if two Nodes are the same.
55 |
56 | Tests if two instances are equal.
57 | """
58 | return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__)
59 |
60 | @abstractmethod
61 | def match(
62 | self, root_value: object, current_value: object
63 | ) -> Generator[MatchData, None, None]: # pragma: no cover abstract method.
64 | """
65 | Abstract method to determine a node match.
66 |
67 | Match the given root and current JSON data structures against this instance.
68 | For each match, yield an instance of the ``jsonpath2.node.MatchData`` class.
69 | """
70 | raise NotImplementedError()
71 |
--------------------------------------------------------------------------------
/jsonpath2/nodes/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Nodes module contains all the node definitions."""
4 |
--------------------------------------------------------------------------------
/jsonpath2/nodes/current.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """The current node module."""
4 | from typing import Generator
5 | from jsonpath2.node import MatchData, Node
6 |
7 |
8 | class CurrentNode(Node):
9 | """Current node class to store current node info."""
10 |
11 | def __init__(self, next_node: Node):
12 | """Save the current node."""
13 | super(CurrentNode, self).__init__()
14 | self.next_node = next_node
15 |
16 | def __jsonpath__(self) -> Generator[str, None, None]:
17 | """Return the current node string."""
18 | yield "@"
19 | for next_node_token in self.next_node.__jsonpath__():
20 | yield next_node_token
21 |
22 | def match(
23 | self, root_value: object, current_value: object
24 | ) -> Generator[MatchData, None, None]:
25 | """Match the current value and root value."""
26 | return map(
27 | lambda next_node_match_data: MatchData(
28 | CurrentNode(next_node_match_data.node),
29 | next_node_match_data.root_value,
30 | next_node_match_data.current_value,
31 | ),
32 | self.next_node.match(root_value, current_value),
33 | )
34 |
--------------------------------------------------------------------------------
/jsonpath2/nodes/recursivedescent.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Recursive descent module."""
4 | import itertools
5 | from typing import Generator
6 | from jsonpath2.node import MatchData, Node
7 | from jsonpath2.nodes.subscript import SubscriptNode
8 | from jsonpath2.subscripts.wildcard import WildcardSubscript
9 |
10 |
11 | class RecursiveDescentNode(Node):
12 | """Recursive descent node class."""
13 |
14 | def __init__(self, next_node: Node):
15 | """Save the next node."""
16 | super(RecursiveDescentNode, self).__init__()
17 | self.next_node = next_node
18 |
19 | def __jsonpath__(self) -> Generator[str, None, None]:
20 | """Dump the string for the previous node."""
21 | yield ".."
22 | for next_node_token in self.next_node.__jsonpath__():
23 | yield next_node_token
24 |
25 | def match(
26 | self, root_value: object, current_value: object
27 | ) -> Generator[MatchData, None, None]:
28 | """Match the root value with the current value."""
29 | # NOTE Depth-first
30 | return itertools.chain(
31 | self.next_node.match(root_value, current_value),
32 | SubscriptNode(self, [WildcardSubscript()]).match(root_value, current_value),
33 | )
34 |
--------------------------------------------------------------------------------
/jsonpath2/nodes/root.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Root node type."""
4 | from typing import Generator
5 | from jsonpath2.node import MatchData, Node
6 |
7 |
8 | class RootNode(Node):
9 | """Root node to start the process."""
10 |
11 | def __init__(self, next_node: Node):
12 | """Save the next node object."""
13 | super(RootNode, self).__init__()
14 | self.next_node = next_node
15 |
16 | def match(
17 | self, root_value: object, current_value: object
18 | ) -> Generator[MatchData, None, None]:
19 | """Match the root value with the current value."""
20 | return map(
21 | lambda next_node_match_data: MatchData(
22 | RootNode(next_node_match_data.node),
23 | next_node_match_data.root_value,
24 | next_node_match_data.current_value,
25 | ),
26 | self.next_node.match(root_value, root_value),
27 | )
28 |
29 | def __jsonpath__(self) -> Generator[str, None, None]:
30 | """Yield the start character string and the next node."""
31 | yield "$"
32 | for next_node_token in self.next_node.__jsonpath__():
33 | yield next_node_token
34 |
--------------------------------------------------------------------------------
/jsonpath2/nodes/subscript.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """The subscript module."""
4 | from typing import Generator, List
5 | from jsonpath2.node import MatchData, Node
6 | from jsonpath2.subscript import Subscript
7 | from jsonpath2.nodes.terminal import TerminalNode
8 |
9 |
10 | class SubscriptNode(Node):
11 | """The subscript node class to handle '[]'."""
12 |
13 | def __init__(self, next_node: Node, subscripts: List[Subscript] = None):
14 | """Save the next node and subscripts."""
15 | super(SubscriptNode, self).__init__()
16 | self.next_node = next_node
17 | self.subscripts = subscripts if subscripts else []
18 |
19 | def __jsonpath__(self) -> Generator[str, None, None]:
20 | """Yield the subscript characters and subscript classes."""
21 | yield "["
22 | for subscript_index, subscript in enumerate(self.subscripts):
23 | if subscript_index > 0:
24 | yield ","
25 | for subscript_token in subscript.__jsonpath__():
26 | yield subscript_token
27 | yield "]"
28 | for next_node_token in self.next_node.__jsonpath__():
29 | yield next_node_token
30 |
31 | def match(
32 | self, root_value: object, current_value: object
33 | ) -> Generator[MatchData, None, None]:
34 | """Match root value and current value for subscripts."""
35 | for subscript in self.subscripts:
36 | for subscript_match_data in subscript.match(root_value, current_value):
37 | for next_node_match_data in self.next_node.match(
38 | subscript_match_data.root_value, subscript_match_data.current_value
39 | ):
40 | if isinstance(subscript_match_data.node, TerminalNode):
41 | yield next_node_match_data
42 | elif isinstance(subscript_match_data.node, SubscriptNode):
43 | if isinstance(
44 | subscript_match_data.node.next_node, TerminalNode
45 | ):
46 | yield MatchData(
47 | SubscriptNode(
48 | next_node_match_data.node,
49 | subscript_match_data.node.subscripts,
50 | ),
51 | next_node_match_data.root_value,
52 | next_node_match_data.current_value,
53 | )
54 | else:
55 | raise ValueError()
56 | else:
57 | raise ValueError()
58 |
--------------------------------------------------------------------------------
/jsonpath2/nodes/terminal.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Terminal node object."""
4 | from typing import Generator
5 | from jsonpath2.node import MatchData, Node
6 |
7 |
8 | class TerminalNode(Node):
9 | """Terminal node class."""
10 |
11 | def __jsonpath__(self) -> Generator[str, None, None]:
12 | """Return the empty array not yield."""
13 | return []
14 |
15 | def match(
16 | self, root_value: object, current_value: object
17 | ) -> Generator[MatchData, None, None]:
18 | """Match a termainal node."""
19 | return [MatchData(self, root_value, current_value)]
20 |
--------------------------------------------------------------------------------
/jsonpath2/parser/JSONPath.g4:
--------------------------------------------------------------------------------
1 | grammar JSONPath;
2 |
3 | CURRENT_VALUE : '@' ;
4 | RECURSIVE_DESCENT : '..' ;
5 | ROOT_VALUE : '$' ;
6 | SUBSCRIPT : '.' ;
7 | WILDCARD_SUBSCRIPT : '*' ;
8 |
9 | AND : 'and' ;
10 | EQ : '=' ;
11 | GE : '>=' ;
12 | GT : '>' ;
13 | LE : '<=' ;
14 | LT : '<' ;
15 | NE : '!=' ;
16 | NOT : 'not' ;
17 | OR : 'or' ;
18 | CN : 'contains' ;
19 |
20 | TRUE : 'true' ;
21 | FALSE : 'false' ;
22 | NULL : 'null' ;
23 |
24 | BRACE_LEFT : '{' ;
25 | BRACE_RIGHT : '}' ;
26 | BRACKET_LEFT : '[' ;
27 | BRACKET_RIGHT : ']' ;
28 | COLON : ':' ;
29 | COMMA : ',' ;
30 | PAREN_LEFT : '(' ;
31 | PAREN_RIGHT : ')' ;
32 | QUESTION : '?' ;
33 |
34 | jsonpath
35 | : ROOT_VALUE subscript? EOF
36 | ;
37 |
38 | jsonpath_
39 | : ( ROOT_VALUE | CURRENT_VALUE ) subscript?
40 | ;
41 |
42 | jsonpath__
43 | : jsonpath_
44 | | value
45 | ;
46 |
47 |
48 | subscript
49 | : RECURSIVE_DESCENT ( subscriptableBareword | subscriptables ) subscript?
50 | | SUBSCRIPT subscriptableBareword subscript?
51 | | subscriptables subscript?
52 | ;
53 |
54 | subscriptables
55 | : BRACKET_LEFT subscriptable ( COMMA subscriptable )* BRACKET_RIGHT
56 | ;
57 |
58 | subscriptableArguments
59 | : PAREN_LEFT ( jsonpath__ ( COMMA jsonpath__ )* )? PAREN_RIGHT
60 | ;
61 |
62 | subscriptableBareword
63 | : ID subscriptableArguments?
64 | | WILDCARD_SUBSCRIPT
65 | ;
66 |
67 | subscriptable
68 | : STRING
69 | | NUMBER{self.tryCast(int)}? sliceable?
70 | | sliceable
71 | | WILDCARD_SUBSCRIPT
72 | | QUESTION PAREN_LEFT expression PAREN_RIGHT
73 | | jsonpath_
74 | | ID subscriptableArguments
75 | ;
76 |
77 | sliceable
78 | : COLON ( NUMBER{self.tryCast(int)}? )? ( COLON ( NUMBER{self.tryCast(int)}? )? )?
79 | ;
80 |
81 | expression
82 | : andExpression
83 | ;
84 |
85 | andExpression
86 | : orExpression ( AND andExpression )?
87 | ;
88 |
89 | orExpression
90 | : notExpression ( OR orExpression )?
91 | ;
92 |
93 | notExpression
94 | : NOT notExpression
95 | | PAREN_LEFT expression PAREN_RIGHT
96 | | jsonpath__ ( ( EQ | NE | LT | LE | GT | GE | CN ) jsonpath__ )?
97 | ;
98 |
99 |
100 | ID
101 | : [_A-Za-z] [_A-Za-z0-9]*
102 | ;
103 |
104 |
105 | /* c.f., https://github.com/antlr/grammars-v4/blob/master/json/JSON.g4 */
106 |
107 | json
108 | : value
109 | ;
110 |
111 | obj
112 | : BRACE_LEFT pair ( COMMA pair )* BRACE_RIGHT
113 | | BRACE_LEFT BRACE_RIGHT
114 | ;
115 |
116 | pair
117 | : STRING COLON value
118 | ;
119 |
120 | array
121 | : BRACKET_LEFT value ( COMMA value )* BRACKET_RIGHT
122 | | BRACKET_LEFT BRACKET_RIGHT
123 | ;
124 |
125 | value
126 | : STRING
127 | | NUMBER
128 | | obj
129 | | array
130 | | TRUE
131 | | FALSE
132 | | NULL
133 | ;
134 |
135 |
136 | STRING
137 | : '"' (ESC | SAFECODEPOINT)* '"'
138 | ;
139 |
140 |
141 | fragment ESC
142 | : '\\' (["\\/bfnrt] | UNICODE)
143 | ;
144 | fragment UNICODE
145 | : 'u' HEX HEX HEX HEX
146 | ;
147 | fragment HEX
148 | : [0-9a-fA-F]
149 | ;
150 | fragment SAFECODEPOINT
151 | : ~ ["\\\u0000-\u001F]
152 | ;
153 |
154 |
155 | NUMBER
156 | : '-'? INT ('.' [0-9] +)? EXP?
157 | ;
158 |
159 |
160 | fragment INT
161 | : '0' | [1-9] [0-9]*
162 | ;
163 |
164 | // no leading zeros
165 |
166 | fragment EXP
167 | : [Ee] [+\-]? INT
168 | ;
169 |
170 | // \- since - means "range" inside [...]
171 |
172 | WS
173 | : [ \t\n\r] + -> skip
174 | ;
175 |
--------------------------------------------------------------------------------
/jsonpath2/parser/JSONPath.interp:
--------------------------------------------------------------------------------
1 | token literal names:
2 | null
3 | '@'
4 | '..'
5 | '$'
6 | '.'
7 | '*'
8 | 'and'
9 | '='
10 | '>='
11 | '>'
12 | '<='
13 | '<'
14 | '!='
15 | 'not'
16 | 'or'
17 | 'contains'
18 | 'true'
19 | 'false'
20 | 'null'
21 | '{'
22 | '}'
23 | '['
24 | ']'
25 | ':'
26 | ','
27 | '('
28 | ')'
29 | '?'
30 | null
31 | null
32 | null
33 | null
34 |
35 | token symbolic names:
36 | null
37 | CURRENT_VALUE
38 | RECURSIVE_DESCENT
39 | ROOT_VALUE
40 | SUBSCRIPT
41 | WILDCARD_SUBSCRIPT
42 | AND
43 | EQ
44 | GE
45 | GT
46 | LE
47 | LT
48 | NE
49 | NOT
50 | OR
51 | CN
52 | TRUE
53 | FALSE
54 | NULL
55 | BRACE_LEFT
56 | BRACE_RIGHT
57 | BRACKET_LEFT
58 | BRACKET_RIGHT
59 | COLON
60 | COMMA
61 | PAREN_LEFT
62 | PAREN_RIGHT
63 | QUESTION
64 | ID
65 | STRING
66 | NUMBER
67 | WS
68 |
69 | rule names:
70 | jsonpath
71 | jsonpath_
72 | jsonpath__
73 | subscript
74 | subscriptables
75 | subscriptableArguments
76 | subscriptableBareword
77 | subscriptable
78 | sliceable
79 | expression
80 | andExpression
81 | orExpression
82 | notExpression
83 | json
84 | obj
85 | pair
86 | array
87 | value
88 |
89 |
90 | atn:
91 | [4, 1, 31, 201, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 1, 0, 1, 0, 3, 0, 39, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 45, 8, 1, 1, 2, 1, 2, 3, 2, 49, 8, 2, 1, 3, 1, 3, 1, 3, 3, 3, 54, 8, 3, 1, 3, 3, 3, 57, 8, 3, 1, 3, 1, 3, 1, 3, 3, 3, 62, 8, 3, 1, 3, 1, 3, 3, 3, 66, 8, 3, 3, 3, 68, 8, 3, 1, 4, 1, 4, 1, 4, 1, 4, 5, 4, 74, 8, 4, 10, 4, 12, 4, 77, 9, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 85, 8, 5, 10, 5, 12, 5, 88, 9, 5, 3, 5, 90, 8, 5, 1, 5, 1, 5, 1, 6, 1, 6, 3, 6, 96, 8, 6, 1, 6, 3, 6, 99, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 105, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 117, 8, 7, 1, 8, 1, 8, 1, 8, 3, 8, 122, 8, 8, 1, 8, 1, 8, 1, 8, 3, 8, 127, 8, 8, 3, 8, 129, 8, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 3, 10, 136, 8, 10, 1, 11, 1, 11, 1, 11, 3, 11, 141, 8, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 3, 12, 152, 8, 12, 3, 12, 154, 8, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 5, 14, 162, 8, 14, 10, 14, 12, 14, 165, 9, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 171, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 181, 8, 16, 10, 16, 12, 16, 184, 9, 16, 1, 16, 1, 16, 1, 16, 1, 16, 3, 16, 190, 8, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 199, 8, 17, 1, 17, 0, 0, 18, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 0, 2, 2, 0, 1, 1, 3, 3, 2, 0, 7, 12, 15, 15, 221, 0, 36, 1, 0, 0, 0, 2, 42, 1, 0, 0, 0, 4, 48, 1, 0, 0, 0, 6, 67, 1, 0, 0, 0, 8, 69, 1, 0, 0, 0, 10, 80, 1, 0, 0, 0, 12, 98, 1, 0, 0, 0, 14, 116, 1, 0, 0, 0, 16, 118, 1, 0, 0, 0, 18, 130, 1, 0, 0, 0, 20, 132, 1, 0, 0, 0, 22, 137, 1, 0, 0, 0, 24, 153, 1, 0, 0, 0, 26, 155, 1, 0, 0, 0, 28, 170, 1, 0, 0, 0, 30, 172, 1, 0, 0, 0, 32, 189, 1, 0, 0, 0, 34, 198, 1, 0, 0, 0, 36, 38, 5, 3, 0, 0, 37, 39, 3, 6, 3, 0, 38, 37, 1, 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 40, 1, 0, 0, 0, 40, 41, 5, 0, 0, 1, 41, 1, 1, 0, 0, 0, 42, 44, 7, 0, 0, 0, 43, 45, 3, 6, 3, 0, 44, 43, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 3, 1, 0, 0, 0, 46, 49, 3, 2, 1, 0, 47, 49, 3, 34, 17, 0, 48, 46, 1, 0, 0, 0, 48, 47, 1, 0, 0, 0, 49, 5, 1, 0, 0, 0, 50, 53, 5, 2, 0, 0, 51, 54, 3, 12, 6, 0, 52, 54, 3, 8, 4, 0, 53, 51, 1, 0, 0, 0, 53, 52, 1, 0, 0, 0, 54, 56, 1, 0, 0, 0, 55, 57, 3, 6, 3, 0, 56, 55, 1, 0, 0, 0, 56, 57, 1, 0, 0, 0, 57, 68, 1, 0, 0, 0, 58, 59, 5, 4, 0, 0, 59, 61, 3, 12, 6, 0, 60, 62, 3, 6, 3, 0, 61, 60, 1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 68, 1, 0, 0, 0, 63, 65, 3, 8, 4, 0, 64, 66, 3, 6, 3, 0, 65, 64, 1, 0, 0, 0, 65, 66, 1, 0, 0, 0, 66, 68, 1, 0, 0, 0, 67, 50, 1, 0, 0, 0, 67, 58, 1, 0, 0, 0, 67, 63, 1, 0, 0, 0, 68, 7, 1, 0, 0, 0, 69, 70, 5, 21, 0, 0, 70, 75, 3, 14, 7, 0, 71, 72, 5, 24, 0, 0, 72, 74, 3, 14, 7, 0, 73, 71, 1, 0, 0, 0, 74, 77, 1, 0, 0, 0, 75, 73, 1, 0, 0, 0, 75, 76, 1, 0, 0, 0, 76, 78, 1, 0, 0, 0, 77, 75, 1, 0, 0, 0, 78, 79, 5, 22, 0, 0, 79, 9, 1, 0, 0, 0, 80, 89, 5, 25, 0, 0, 81, 86, 3, 4, 2, 0, 82, 83, 5, 24, 0, 0, 83, 85, 3, 4, 2, 0, 84, 82, 1, 0, 0, 0, 85, 88, 1, 0, 0, 0, 86, 84, 1, 0, 0, 0, 86, 87, 1, 0, 0, 0, 87, 90, 1, 0, 0, 0, 88, 86, 1, 0, 0, 0, 89, 81, 1, 0, 0, 0, 89, 90, 1, 0, 0, 0, 90, 91, 1, 0, 0, 0, 91, 92, 5, 26, 0, 0, 92, 11, 1, 0, 0, 0, 93, 95, 5, 28, 0, 0, 94, 96, 3, 10, 5, 0, 95, 94, 1, 0, 0, 0, 95, 96, 1, 0, 0, 0, 96, 99, 1, 0, 0, 0, 97, 99, 5, 5, 0, 0, 98, 93, 1, 0, 0, 0, 98, 97, 1, 0, 0, 0, 99, 13, 1, 0, 0, 0, 100, 117, 5, 29, 0, 0, 101, 102, 5, 30, 0, 0, 102, 104, 4, 7, 0, 0, 103, 105, 3, 16, 8, 0, 104, 103, 1, 0, 0, 0, 104, 105, 1, 0, 0, 0, 105, 117, 1, 0, 0, 0, 106, 117, 3, 16, 8, 0, 107, 117, 5, 5, 0, 0, 108, 109, 5, 27, 0, 0, 109, 110, 5, 25, 0, 0, 110, 111, 3, 18, 9, 0, 111, 112, 5, 26, 0, 0, 112, 117, 1, 0, 0, 0, 113, 117, 3, 2, 1, 0, 114, 115, 5, 28, 0, 0, 115, 117, 3, 10, 5, 0, 116, 100, 1, 0, 0, 0, 116, 101, 1, 0, 0, 0, 116, 106, 1, 0, 0, 0, 116, 107, 1, 0, 0, 0, 116, 108, 1, 0, 0, 0, 116, 113, 1, 0, 0, 0, 116, 114, 1, 0, 0, 0, 117, 15, 1, 0, 0, 0, 118, 121, 5, 23, 0, 0, 119, 120, 5, 30, 0, 0, 120, 122, 4, 8, 1, 0, 121, 119, 1, 0, 0, 0, 121, 122, 1, 0, 0, 0, 122, 128, 1, 0, 0, 0, 123, 126, 5, 23, 0, 0, 124, 125, 5, 30, 0, 0, 125, 127, 4, 8, 2, 0, 126, 124, 1, 0, 0, 0, 126, 127, 1, 0, 0, 0, 127, 129, 1, 0, 0, 0, 128, 123, 1, 0, 0, 0, 128, 129, 1, 0, 0, 0, 129, 17, 1, 0, 0, 0, 130, 131, 3, 20, 10, 0, 131, 19, 1, 0, 0, 0, 132, 135, 3, 22, 11, 0, 133, 134, 5, 6, 0, 0, 134, 136, 3, 20, 10, 0, 135, 133, 1, 0, 0, 0, 135, 136, 1, 0, 0, 0, 136, 21, 1, 0, 0, 0, 137, 140, 3, 24, 12, 0, 138, 139, 5, 14, 0, 0, 139, 141, 3, 22, 11, 0, 140, 138, 1, 0, 0, 0, 140, 141, 1, 0, 0, 0, 141, 23, 1, 0, 0, 0, 142, 143, 5, 13, 0, 0, 143, 154, 3, 24, 12, 0, 144, 145, 5, 25, 0, 0, 145, 146, 3, 18, 9, 0, 146, 147, 5, 26, 0, 0, 147, 154, 1, 0, 0, 0, 148, 151, 3, 4, 2, 0, 149, 150, 7, 1, 0, 0, 150, 152, 3, 4, 2, 0, 151, 149, 1, 0, 0, 0, 151, 152, 1, 0, 0, 0, 152, 154, 1, 0, 0, 0, 153, 142, 1, 0, 0, 0, 153, 144, 1, 0, 0, 0, 153, 148, 1, 0, 0, 0, 154, 25, 1, 0, 0, 0, 155, 156, 3, 34, 17, 0, 156, 27, 1, 0, 0, 0, 157, 158, 5, 19, 0, 0, 158, 163, 3, 30, 15, 0, 159, 160, 5, 24, 0, 0, 160, 162, 3, 30, 15, 0, 161, 159, 1, 0, 0, 0, 162, 165, 1, 0, 0, 0, 163, 161, 1, 0, 0, 0, 163, 164, 1, 0, 0, 0, 164, 166, 1, 0, 0, 0, 165, 163, 1, 0, 0, 0, 166, 167, 5, 20, 0, 0, 167, 171, 1, 0, 0, 0, 168, 169, 5, 19, 0, 0, 169, 171, 5, 20, 0, 0, 170, 157, 1, 0, 0, 0, 170, 168, 1, 0, 0, 0, 171, 29, 1, 0, 0, 0, 172, 173, 5, 29, 0, 0, 173, 174, 5, 23, 0, 0, 174, 175, 3, 34, 17, 0, 175, 31, 1, 0, 0, 0, 176, 177, 5, 21, 0, 0, 177, 182, 3, 34, 17, 0, 178, 179, 5, 24, 0, 0, 179, 181, 3, 34, 17, 0, 180, 178, 1, 0, 0, 0, 181, 184, 1, 0, 0, 0, 182, 180, 1, 0, 0, 0, 182, 183, 1, 0, 0, 0, 183, 185, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 186, 5, 22, 0, 0, 186, 190, 1, 0, 0, 0, 187, 188, 5, 21, 0, 0, 188, 190, 5, 22, 0, 0, 189, 176, 1, 0, 0, 0, 189, 187, 1, 0, 0, 0, 190, 33, 1, 0, 0, 0, 191, 199, 5, 29, 0, 0, 192, 199, 5, 30, 0, 0, 193, 199, 3, 28, 14, 0, 194, 199, 3, 32, 16, 0, 195, 199, 5, 16, 0, 0, 196, 199, 5, 17, 0, 0, 197, 199, 5, 18, 0, 0, 198, 191, 1, 0, 0, 0, 198, 192, 1, 0, 0, 0, 198, 193, 1, 0, 0, 0, 198, 194, 1, 0, 0, 0, 198, 195, 1, 0, 0, 0, 198, 196, 1, 0, 0, 0, 198, 197, 1, 0, 0, 0, 199, 35, 1, 0, 0, 0, 27, 38, 44, 48, 53, 56, 61, 65, 67, 75, 86, 89, 95, 98, 104, 116, 121, 126, 128, 135, 140, 151, 153, 163, 170, 182, 189, 198]
--------------------------------------------------------------------------------
/jsonpath2/parser/JSONPath.tokens:
--------------------------------------------------------------------------------
1 | CURRENT_VALUE=1
2 | RECURSIVE_DESCENT=2
3 | ROOT_VALUE=3
4 | SUBSCRIPT=4
5 | WILDCARD_SUBSCRIPT=5
6 | AND=6
7 | EQ=7
8 | GE=8
9 | GT=9
10 | LE=10
11 | LT=11
12 | NE=12
13 | NOT=13
14 | OR=14
15 | CN=15
16 | TRUE=16
17 | FALSE=17
18 | NULL=18
19 | BRACE_LEFT=19
20 | BRACE_RIGHT=20
21 | BRACKET_LEFT=21
22 | BRACKET_RIGHT=22
23 | COLON=23
24 | COMMA=24
25 | PAREN_LEFT=25
26 | PAREN_RIGHT=26
27 | QUESTION=27
28 | ID=28
29 | STRING=29
30 | NUMBER=30
31 | WS=31
32 | '@'=1
33 | '..'=2
34 | '$'=3
35 | '.'=4
36 | '*'=5
37 | 'and'=6
38 | '='=7
39 | '>='=8
40 | '>'=9
41 | '<='=10
42 | '<'=11
43 | '!='=12
44 | 'not'=13
45 | 'or'=14
46 | 'contains'=15
47 | 'true'=16
48 | 'false'=17
49 | 'null'=18
50 | '{'=19
51 | '}'=20
52 | '['=21
53 | ']'=22
54 | ':'=23
55 | ','=24
56 | '('=25
57 | ')'=26
58 | '?'=27
59 |
--------------------------------------------------------------------------------
/jsonpath2/parser/JSONPathLexer.interp:
--------------------------------------------------------------------------------
1 | token literal names:
2 | null
3 | '@'
4 | '..'
5 | '$'
6 | '.'
7 | '*'
8 | 'and'
9 | '='
10 | '>='
11 | '>'
12 | '<='
13 | '<'
14 | '!='
15 | 'not'
16 | 'or'
17 | 'contains'
18 | 'true'
19 | 'false'
20 | 'null'
21 | '{'
22 | '}'
23 | '['
24 | ']'
25 | ':'
26 | ','
27 | '('
28 | ')'
29 | '?'
30 | null
31 | null
32 | null
33 | null
34 |
35 | token symbolic names:
36 | null
37 | CURRENT_VALUE
38 | RECURSIVE_DESCENT
39 | ROOT_VALUE
40 | SUBSCRIPT
41 | WILDCARD_SUBSCRIPT
42 | AND
43 | EQ
44 | GE
45 | GT
46 | LE
47 | LT
48 | NE
49 | NOT
50 | OR
51 | CN
52 | TRUE
53 | FALSE
54 | NULL
55 | BRACE_LEFT
56 | BRACE_RIGHT
57 | BRACKET_LEFT
58 | BRACKET_RIGHT
59 | COLON
60 | COMMA
61 | PAREN_LEFT
62 | PAREN_RIGHT
63 | QUESTION
64 | ID
65 | STRING
66 | NUMBER
67 | WS
68 |
69 | rule names:
70 | CURRENT_VALUE
71 | RECURSIVE_DESCENT
72 | ROOT_VALUE
73 | SUBSCRIPT
74 | WILDCARD_SUBSCRIPT
75 | AND
76 | EQ
77 | GE
78 | GT
79 | LE
80 | LT
81 | NE
82 | NOT
83 | OR
84 | CN
85 | TRUE
86 | FALSE
87 | NULL
88 | BRACE_LEFT
89 | BRACE_RIGHT
90 | BRACKET_LEFT
91 | BRACKET_RIGHT
92 | COLON
93 | COMMA
94 | PAREN_LEFT
95 | PAREN_RIGHT
96 | QUESTION
97 | ID
98 | STRING
99 | ESC
100 | UNICODE
101 | HEX
102 | SAFECODEPOINT
103 | NUMBER
104 | INT
105 | EXP
106 | WS
107 |
108 | channel names:
109 | DEFAULT_TOKEN_CHANNEL
110 | HIDDEN
111 |
112 | mode names:
113 | DEFAULT_MODE
114 |
115 | atn:
116 | [4, 0, 31, 225, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 5, 27, 158, 8, 27, 10, 27, 12, 27, 161, 9, 27, 1, 28, 1, 28, 1, 28, 5, 28, 166, 8, 28, 10, 28, 12, 28, 169, 9, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 3, 29, 176, 8, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 3, 33, 189, 8, 33, 1, 33, 1, 33, 1, 33, 4, 33, 194, 8, 33, 11, 33, 12, 33, 195, 3, 33, 198, 8, 33, 1, 33, 3, 33, 201, 8, 33, 1, 34, 1, 34, 1, 34, 5, 34, 206, 8, 34, 10, 34, 12, 34, 209, 9, 34, 3, 34, 211, 8, 34, 1, 35, 1, 35, 3, 35, 215, 8, 35, 1, 35, 1, 35, 1, 36, 4, 36, 220, 8, 36, 11, 36, 12, 36, 221, 1, 36, 1, 36, 0, 0, 37, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 0, 61, 0, 63, 0, 65, 0, 67, 30, 69, 0, 71, 0, 73, 31, 1, 0, 10, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 8, 0, 34, 34, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 3, 0, 0, 31, 34, 34, 92, 92, 1, 0, 48, 57, 1, 0, 49, 57, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 3, 0, 9, 10, 13, 13, 32, 32, 230, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 1, 75, 1, 0, 0, 0, 3, 77, 1, 0, 0, 0, 5, 80, 1, 0, 0, 0, 7, 82, 1, 0, 0, 0, 9, 84, 1, 0, 0, 0, 11, 86, 1, 0, 0, 0, 13, 90, 1, 0, 0, 0, 15, 92, 1, 0, 0, 0, 17, 95, 1, 0, 0, 0, 19, 97, 1, 0, 0, 0, 21, 100, 1, 0, 0, 0, 23, 102, 1, 0, 0, 0, 25, 105, 1, 0, 0, 0, 27, 109, 1, 0, 0, 0, 29, 112, 1, 0, 0, 0, 31, 121, 1, 0, 0, 0, 33, 126, 1, 0, 0, 0, 35, 132, 1, 0, 0, 0, 37, 137, 1, 0, 0, 0, 39, 139, 1, 0, 0, 0, 41, 141, 1, 0, 0, 0, 43, 143, 1, 0, 0, 0, 45, 145, 1, 0, 0, 0, 47, 147, 1, 0, 0, 0, 49, 149, 1, 0, 0, 0, 51, 151, 1, 0, 0, 0, 53, 153, 1, 0, 0, 0, 55, 155, 1, 0, 0, 0, 57, 162, 1, 0, 0, 0, 59, 172, 1, 0, 0, 0, 61, 177, 1, 0, 0, 0, 63, 183, 1, 0, 0, 0, 65, 185, 1, 0, 0, 0, 67, 188, 1, 0, 0, 0, 69, 210, 1, 0, 0, 0, 71, 212, 1, 0, 0, 0, 73, 219, 1, 0, 0, 0, 75, 76, 5, 64, 0, 0, 76, 2, 1, 0, 0, 0, 77, 78, 5, 46, 0, 0, 78, 79, 5, 46, 0, 0, 79, 4, 1, 0, 0, 0, 80, 81, 5, 36, 0, 0, 81, 6, 1, 0, 0, 0, 82, 83, 5, 46, 0, 0, 83, 8, 1, 0, 0, 0, 84, 85, 5, 42, 0, 0, 85, 10, 1, 0, 0, 0, 86, 87, 5, 97, 0, 0, 87, 88, 5, 110, 0, 0, 88, 89, 5, 100, 0, 0, 89, 12, 1, 0, 0, 0, 90, 91, 5, 61, 0, 0, 91, 14, 1, 0, 0, 0, 92, 93, 5, 62, 0, 0, 93, 94, 5, 61, 0, 0, 94, 16, 1, 0, 0, 0, 95, 96, 5, 62, 0, 0, 96, 18, 1, 0, 0, 0, 97, 98, 5, 60, 0, 0, 98, 99, 5, 61, 0, 0, 99, 20, 1, 0, 0, 0, 100, 101, 5, 60, 0, 0, 101, 22, 1, 0, 0, 0, 102, 103, 5, 33, 0, 0, 103, 104, 5, 61, 0, 0, 104, 24, 1, 0, 0, 0, 105, 106, 5, 110, 0, 0, 106, 107, 5, 111, 0, 0, 107, 108, 5, 116, 0, 0, 108, 26, 1, 0, 0, 0, 109, 110, 5, 111, 0, 0, 110, 111, 5, 114, 0, 0, 111, 28, 1, 0, 0, 0, 112, 113, 5, 99, 0, 0, 113, 114, 5, 111, 0, 0, 114, 115, 5, 110, 0, 0, 115, 116, 5, 116, 0, 0, 116, 117, 5, 97, 0, 0, 117, 118, 5, 105, 0, 0, 118, 119, 5, 110, 0, 0, 119, 120, 5, 115, 0, 0, 120, 30, 1, 0, 0, 0, 121, 122, 5, 116, 0, 0, 122, 123, 5, 114, 0, 0, 123, 124, 5, 117, 0, 0, 124, 125, 5, 101, 0, 0, 125, 32, 1, 0, 0, 0, 126, 127, 5, 102, 0, 0, 127, 128, 5, 97, 0, 0, 128, 129, 5, 108, 0, 0, 129, 130, 5, 115, 0, 0, 130, 131, 5, 101, 0, 0, 131, 34, 1, 0, 0, 0, 132, 133, 5, 110, 0, 0, 133, 134, 5, 117, 0, 0, 134, 135, 5, 108, 0, 0, 135, 136, 5, 108, 0, 0, 136, 36, 1, 0, 0, 0, 137, 138, 5, 123, 0, 0, 138, 38, 1, 0, 0, 0, 139, 140, 5, 125, 0, 0, 140, 40, 1, 0, 0, 0, 141, 142, 5, 91, 0, 0, 142, 42, 1, 0, 0, 0, 143, 144, 5, 93, 0, 0, 144, 44, 1, 0, 0, 0, 145, 146, 5, 58, 0, 0, 146, 46, 1, 0, 0, 0, 147, 148, 5, 44, 0, 0, 148, 48, 1, 0, 0, 0, 149, 150, 5, 40, 0, 0, 150, 50, 1, 0, 0, 0, 151, 152, 5, 41, 0, 0, 152, 52, 1, 0, 0, 0, 153, 154, 5, 63, 0, 0, 154, 54, 1, 0, 0, 0, 155, 159, 7, 0, 0, 0, 156, 158, 7, 1, 0, 0, 157, 156, 1, 0, 0, 0, 158, 161, 1, 0, 0, 0, 159, 157, 1, 0, 0, 0, 159, 160, 1, 0, 0, 0, 160, 56, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 162, 167, 5, 34, 0, 0, 163, 166, 3, 59, 29, 0, 164, 166, 3, 65, 32, 0, 165, 163, 1, 0, 0, 0, 165, 164, 1, 0, 0, 0, 166, 169, 1, 0, 0, 0, 167, 165, 1, 0, 0, 0, 167, 168, 1, 0, 0, 0, 168, 170, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 170, 171, 5, 34, 0, 0, 171, 58, 1, 0, 0, 0, 172, 175, 5, 92, 0, 0, 173, 176, 7, 2, 0, 0, 174, 176, 3, 61, 30, 0, 175, 173, 1, 0, 0, 0, 175, 174, 1, 0, 0, 0, 176, 60, 1, 0, 0, 0, 177, 178, 5, 117, 0, 0, 178, 179, 3, 63, 31, 0, 179, 180, 3, 63, 31, 0, 180, 181, 3, 63, 31, 0, 181, 182, 3, 63, 31, 0, 182, 62, 1, 0, 0, 0, 183, 184, 7, 3, 0, 0, 184, 64, 1, 0, 0, 0, 185, 186, 8, 4, 0, 0, 186, 66, 1, 0, 0, 0, 187, 189, 5, 45, 0, 0, 188, 187, 1, 0, 0, 0, 188, 189, 1, 0, 0, 0, 189, 190, 1, 0, 0, 0, 190, 197, 3, 69, 34, 0, 191, 193, 5, 46, 0, 0, 192, 194, 7, 5, 0, 0, 193, 192, 1, 0, 0, 0, 194, 195, 1, 0, 0, 0, 195, 193, 1, 0, 0, 0, 195, 196, 1, 0, 0, 0, 196, 198, 1, 0, 0, 0, 197, 191, 1, 0, 0, 0, 197, 198, 1, 0, 0, 0, 198, 200, 1, 0, 0, 0, 199, 201, 3, 71, 35, 0, 200, 199, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 68, 1, 0, 0, 0, 202, 211, 5, 48, 0, 0, 203, 207, 7, 6, 0, 0, 204, 206, 7, 5, 0, 0, 205, 204, 1, 0, 0, 0, 206, 209, 1, 0, 0, 0, 207, 205, 1, 0, 0, 0, 207, 208, 1, 0, 0, 0, 208, 211, 1, 0, 0, 0, 209, 207, 1, 0, 0, 0, 210, 202, 1, 0, 0, 0, 210, 203, 1, 0, 0, 0, 211, 70, 1, 0, 0, 0, 212, 214, 7, 7, 0, 0, 213, 215, 7, 8, 0, 0, 214, 213, 1, 0, 0, 0, 214, 215, 1, 0, 0, 0, 215, 216, 1, 0, 0, 0, 216, 217, 3, 69, 34, 0, 217, 72, 1, 0, 0, 0, 218, 220, 7, 9, 0, 0, 219, 218, 1, 0, 0, 0, 220, 221, 1, 0, 0, 0, 221, 219, 1, 0, 0, 0, 221, 222, 1, 0, 0, 0, 222, 223, 1, 0, 0, 0, 223, 224, 6, 36, 0, 0, 224, 74, 1, 0, 0, 0, 13, 0, 159, 165, 167, 175, 188, 195, 197, 200, 207, 210, 214, 221, 1, 6, 0, 0]
--------------------------------------------------------------------------------
/jsonpath2/parser/JSONPathLexer.py:
--------------------------------------------------------------------------------
1 | # Generated from jsonpath2/parser/JSONPath.g4 by ANTLR 4.10.1
2 | from antlr4 import *
3 | from io import StringIO
4 | import sys
5 | if sys.version_info[1] > 5:
6 | from typing import TextIO
7 | else:
8 | from typing.io import TextIO
9 |
10 |
11 | def serializedATN():
12 | return [
13 | 4,0,31,225,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,
14 | 2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,
15 | 13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,
16 | 19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,
17 | 26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32,7,
18 | 32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,1,0,1,0,1,1,1,1,1,1,1,
19 | 2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,1,5,1,6,1,6,1,7,1,7,1,7,1,8,1,
20 | 8,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,13,
21 | 1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,15,1,15,
22 | 1,15,1,15,1,15,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,17,1,17,
23 | 1,17,1,18,1,18,1,19,1,19,1,20,1,20,1,21,1,21,1,22,1,22,1,23,1,23,
24 | 1,24,1,24,1,25,1,25,1,26,1,26,1,27,1,27,5,27,158,8,27,10,27,12,27,
25 | 161,9,27,1,28,1,28,1,28,5,28,166,8,28,10,28,12,28,169,9,28,1,28,
26 | 1,28,1,29,1,29,1,29,3,29,176,8,29,1,30,1,30,1,30,1,30,1,30,1,30,
27 | 1,31,1,31,1,32,1,32,1,33,3,33,189,8,33,1,33,1,33,1,33,4,33,194,8,
28 | 33,11,33,12,33,195,3,33,198,8,33,1,33,3,33,201,8,33,1,34,1,34,1,
29 | 34,5,34,206,8,34,10,34,12,34,209,9,34,3,34,211,8,34,1,35,1,35,3,
30 | 35,215,8,35,1,35,1,35,1,36,4,36,220,8,36,11,36,12,36,221,1,36,1,
31 | 36,0,0,37,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,
32 | 12,25,13,27,14,29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,22,45,
33 | 23,47,24,49,25,51,26,53,27,55,28,57,29,59,0,61,0,63,0,65,0,67,30,
34 | 69,0,71,0,73,31,1,0,10,3,0,65,90,95,95,97,122,4,0,48,57,65,90,95,
35 | 95,97,122,8,0,34,34,47,47,92,92,98,98,102,102,110,110,114,114,116,
36 | 116,3,0,48,57,65,70,97,102,3,0,0,31,34,34,92,92,1,0,48,57,1,0,49,
37 | 57,2,0,69,69,101,101,2,0,43,43,45,45,3,0,9,10,13,13,32,32,230,0,
38 | 1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,
39 | 0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,
40 | 0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,
41 | 0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,
42 | 0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,
43 | 0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,67,1,0,0,0,0,73,1,
44 | 0,0,0,1,75,1,0,0,0,3,77,1,0,0,0,5,80,1,0,0,0,7,82,1,0,0,0,9,84,1,
45 | 0,0,0,11,86,1,0,0,0,13,90,1,0,0,0,15,92,1,0,0,0,17,95,1,0,0,0,19,
46 | 97,1,0,0,0,21,100,1,0,0,0,23,102,1,0,0,0,25,105,1,0,0,0,27,109,1,
47 | 0,0,0,29,112,1,0,0,0,31,121,1,0,0,0,33,126,1,0,0,0,35,132,1,0,0,
48 | 0,37,137,1,0,0,0,39,139,1,0,0,0,41,141,1,0,0,0,43,143,1,0,0,0,45,
49 | 145,1,0,0,0,47,147,1,0,0,0,49,149,1,0,0,0,51,151,1,0,0,0,53,153,
50 | 1,0,0,0,55,155,1,0,0,0,57,162,1,0,0,0,59,172,1,0,0,0,61,177,1,0,
51 | 0,0,63,183,1,0,0,0,65,185,1,0,0,0,67,188,1,0,0,0,69,210,1,0,0,0,
52 | 71,212,1,0,0,0,73,219,1,0,0,0,75,76,5,64,0,0,76,2,1,0,0,0,77,78,
53 | 5,46,0,0,78,79,5,46,0,0,79,4,1,0,0,0,80,81,5,36,0,0,81,6,1,0,0,0,
54 | 82,83,5,46,0,0,83,8,1,0,0,0,84,85,5,42,0,0,85,10,1,0,0,0,86,87,5,
55 | 97,0,0,87,88,5,110,0,0,88,89,5,100,0,0,89,12,1,0,0,0,90,91,5,61,
56 | 0,0,91,14,1,0,0,0,92,93,5,62,0,0,93,94,5,61,0,0,94,16,1,0,0,0,95,
57 | 96,5,62,0,0,96,18,1,0,0,0,97,98,5,60,0,0,98,99,5,61,0,0,99,20,1,
58 | 0,0,0,100,101,5,60,0,0,101,22,1,0,0,0,102,103,5,33,0,0,103,104,5,
59 | 61,0,0,104,24,1,0,0,0,105,106,5,110,0,0,106,107,5,111,0,0,107,108,
60 | 5,116,0,0,108,26,1,0,0,0,109,110,5,111,0,0,110,111,5,114,0,0,111,
61 | 28,1,0,0,0,112,113,5,99,0,0,113,114,5,111,0,0,114,115,5,110,0,0,
62 | 115,116,5,116,0,0,116,117,5,97,0,0,117,118,5,105,0,0,118,119,5,110,
63 | 0,0,119,120,5,115,0,0,120,30,1,0,0,0,121,122,5,116,0,0,122,123,5,
64 | 114,0,0,123,124,5,117,0,0,124,125,5,101,0,0,125,32,1,0,0,0,126,127,
65 | 5,102,0,0,127,128,5,97,0,0,128,129,5,108,0,0,129,130,5,115,0,0,130,
66 | 131,5,101,0,0,131,34,1,0,0,0,132,133,5,110,0,0,133,134,5,117,0,0,
67 | 134,135,5,108,0,0,135,136,5,108,0,0,136,36,1,0,0,0,137,138,5,123,
68 | 0,0,138,38,1,0,0,0,139,140,5,125,0,0,140,40,1,0,0,0,141,142,5,91,
69 | 0,0,142,42,1,0,0,0,143,144,5,93,0,0,144,44,1,0,0,0,145,146,5,58,
70 | 0,0,146,46,1,0,0,0,147,148,5,44,0,0,148,48,1,0,0,0,149,150,5,40,
71 | 0,0,150,50,1,0,0,0,151,152,5,41,0,0,152,52,1,0,0,0,153,154,5,63,
72 | 0,0,154,54,1,0,0,0,155,159,7,0,0,0,156,158,7,1,0,0,157,156,1,0,0,
73 | 0,158,161,1,0,0,0,159,157,1,0,0,0,159,160,1,0,0,0,160,56,1,0,0,0,
74 | 161,159,1,0,0,0,162,167,5,34,0,0,163,166,3,59,29,0,164,166,3,65,
75 | 32,0,165,163,1,0,0,0,165,164,1,0,0,0,166,169,1,0,0,0,167,165,1,0,
76 | 0,0,167,168,1,0,0,0,168,170,1,0,0,0,169,167,1,0,0,0,170,171,5,34,
77 | 0,0,171,58,1,0,0,0,172,175,5,92,0,0,173,176,7,2,0,0,174,176,3,61,
78 | 30,0,175,173,1,0,0,0,175,174,1,0,0,0,176,60,1,0,0,0,177,178,5,117,
79 | 0,0,178,179,3,63,31,0,179,180,3,63,31,0,180,181,3,63,31,0,181,182,
80 | 3,63,31,0,182,62,1,0,0,0,183,184,7,3,0,0,184,64,1,0,0,0,185,186,
81 | 8,4,0,0,186,66,1,0,0,0,187,189,5,45,0,0,188,187,1,0,0,0,188,189,
82 | 1,0,0,0,189,190,1,0,0,0,190,197,3,69,34,0,191,193,5,46,0,0,192,194,
83 | 7,5,0,0,193,192,1,0,0,0,194,195,1,0,0,0,195,193,1,0,0,0,195,196,
84 | 1,0,0,0,196,198,1,0,0,0,197,191,1,0,0,0,197,198,1,0,0,0,198,200,
85 | 1,0,0,0,199,201,3,71,35,0,200,199,1,0,0,0,200,201,1,0,0,0,201,68,
86 | 1,0,0,0,202,211,5,48,0,0,203,207,7,6,0,0,204,206,7,5,0,0,205,204,
87 | 1,0,0,0,206,209,1,0,0,0,207,205,1,0,0,0,207,208,1,0,0,0,208,211,
88 | 1,0,0,0,209,207,1,0,0,0,210,202,1,0,0,0,210,203,1,0,0,0,211,70,1,
89 | 0,0,0,212,214,7,7,0,0,213,215,7,8,0,0,214,213,1,0,0,0,214,215,1,
90 | 0,0,0,215,216,1,0,0,0,216,217,3,69,34,0,217,72,1,0,0,0,218,220,7,
91 | 9,0,0,219,218,1,0,0,0,220,221,1,0,0,0,221,219,1,0,0,0,221,222,1,
92 | 0,0,0,222,223,1,0,0,0,223,224,6,36,0,0,224,74,1,0,0,0,13,0,159,165,
93 | 167,175,188,195,197,200,207,210,214,221,1,6,0,0
94 | ]
95 |
96 | class JSONPathLexer(Lexer):
97 |
98 | atn = ATNDeserializer().deserialize(serializedATN())
99 |
100 | decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
101 |
102 | CURRENT_VALUE = 1
103 | RECURSIVE_DESCENT = 2
104 | ROOT_VALUE = 3
105 | SUBSCRIPT = 4
106 | WILDCARD_SUBSCRIPT = 5
107 | AND = 6
108 | EQ = 7
109 | GE = 8
110 | GT = 9
111 | LE = 10
112 | LT = 11
113 | NE = 12
114 | NOT = 13
115 | OR = 14
116 | CN = 15
117 | TRUE = 16
118 | FALSE = 17
119 | NULL = 18
120 | BRACE_LEFT = 19
121 | BRACE_RIGHT = 20
122 | BRACKET_LEFT = 21
123 | BRACKET_RIGHT = 22
124 | COLON = 23
125 | COMMA = 24
126 | PAREN_LEFT = 25
127 | PAREN_RIGHT = 26
128 | QUESTION = 27
129 | ID = 28
130 | STRING = 29
131 | NUMBER = 30
132 | WS = 31
133 |
134 | channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ]
135 |
136 | modeNames = [ "DEFAULT_MODE" ]
137 |
138 | literalNames = [ "",
139 | "'@'", "'..'", "'$'", "'.'", "'*'", "'and'", "'='", "'>='",
140 | "'>'", "'<='", "'<'", "'!='", "'not'", "'or'", "'contains'",
141 | "'true'", "'false'", "'null'", "'{'", "'}'", "'['", "']'", "':'",
142 | "','", "'('", "')'", "'?'" ]
143 |
144 | symbolicNames = [ "",
145 | "CURRENT_VALUE", "RECURSIVE_DESCENT", "ROOT_VALUE", "SUBSCRIPT",
146 | "WILDCARD_SUBSCRIPT", "AND", "EQ", "GE", "GT", "LE", "LT", "NE",
147 | "NOT", "OR", "CN", "TRUE", "FALSE", "NULL", "BRACE_LEFT", "BRACE_RIGHT",
148 | "BRACKET_LEFT", "BRACKET_RIGHT", "COLON", "COMMA", "PAREN_LEFT",
149 | "PAREN_RIGHT", "QUESTION", "ID", "STRING", "NUMBER", "WS" ]
150 |
151 | ruleNames = [ "CURRENT_VALUE", "RECURSIVE_DESCENT", "ROOT_VALUE", "SUBSCRIPT",
152 | "WILDCARD_SUBSCRIPT", "AND", "EQ", "GE", "GT", "LE", "LT",
153 | "NE", "NOT", "OR", "CN", "TRUE", "FALSE", "NULL", "BRACE_LEFT",
154 | "BRACE_RIGHT", "BRACKET_LEFT", "BRACKET_RIGHT", "COLON",
155 | "COMMA", "PAREN_LEFT", "PAREN_RIGHT", "QUESTION", "ID",
156 | "STRING", "ESC", "UNICODE", "HEX", "SAFECODEPOINT", "NUMBER",
157 | "INT", "EXP", "WS" ]
158 |
159 | grammarFileName = "JSONPath.g4"
160 |
161 | def __init__(self, input=None, output:TextIO = sys.stdout):
162 | super().__init__(input, output)
163 | self.checkVersion("4.10.1")
164 | self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
165 | self._actions = None
166 | self._predicates = None
167 |
168 |
169 |
--------------------------------------------------------------------------------
/jsonpath2/parser/JSONPathLexer.tokens:
--------------------------------------------------------------------------------
1 | CURRENT_VALUE=1
2 | RECURSIVE_DESCENT=2
3 | ROOT_VALUE=3
4 | SUBSCRIPT=4
5 | WILDCARD_SUBSCRIPT=5
6 | AND=6
7 | EQ=7
8 | GE=8
9 | GT=9
10 | LE=10
11 | LT=11
12 | NE=12
13 | NOT=13
14 | OR=14
15 | CN=15
16 | TRUE=16
17 | FALSE=17
18 | NULL=18
19 | BRACE_LEFT=19
20 | BRACE_RIGHT=20
21 | BRACKET_LEFT=21
22 | BRACKET_RIGHT=22
23 | COLON=23
24 | COMMA=24
25 | PAREN_LEFT=25
26 | PAREN_RIGHT=26
27 | QUESTION=27
28 | ID=28
29 | STRING=29
30 | NUMBER=30
31 | WS=31
32 | '@'=1
33 | '..'=2
34 | '$'=3
35 | '.'=4
36 | '*'=5
37 | 'and'=6
38 | '='=7
39 | '>='=8
40 | '>'=9
41 | '<='=10
42 | '<'=11
43 | '!='=12
44 | 'not'=13
45 | 'or'=14
46 | 'contains'=15
47 | 'true'=16
48 | 'false'=17
49 | 'null'=18
50 | '{'=19
51 | '}'=20
52 | '['=21
53 | ']'=22
54 | ':'=23
55 | ','=24
56 | '('=25
57 | ')'=26
58 | '?'=27
59 |
--------------------------------------------------------------------------------
/jsonpath2/parser/JSONPathListener.py:
--------------------------------------------------------------------------------
1 | # Generated from jsonpath2/parser/JSONPath.g4 by ANTLR 4.10.1
2 | from antlr4 import *
3 | if __name__ is not None and "." in __name__:
4 | from .JSONPathParser import JSONPathParser
5 | else:
6 | from JSONPathParser import JSONPathParser
7 |
8 | # This class defines a complete listener for a parse tree produced by JSONPathParser.
9 | class JSONPathListener(ParseTreeListener):
10 |
11 | # Enter a parse tree produced by JSONPathParser#jsonpath.
12 | def enterJsonpath(self, ctx:JSONPathParser.JsonpathContext):
13 | pass
14 |
15 | # Exit a parse tree produced by JSONPathParser#jsonpath.
16 | def exitJsonpath(self, ctx:JSONPathParser.JsonpathContext):
17 | pass
18 |
19 |
20 | # Enter a parse tree produced by JSONPathParser#jsonpath_.
21 | def enterJsonpath_(self, ctx:JSONPathParser.Jsonpath_Context):
22 | pass
23 |
24 | # Exit a parse tree produced by JSONPathParser#jsonpath_.
25 | def exitJsonpath_(self, ctx:JSONPathParser.Jsonpath_Context):
26 | pass
27 |
28 |
29 | # Enter a parse tree produced by JSONPathParser#jsonpath__.
30 | def enterJsonpath__(self, ctx:JSONPathParser.Jsonpath__Context):
31 | pass
32 |
33 | # Exit a parse tree produced by JSONPathParser#jsonpath__.
34 | def exitJsonpath__(self, ctx:JSONPathParser.Jsonpath__Context):
35 | pass
36 |
37 |
38 | # Enter a parse tree produced by JSONPathParser#subscript.
39 | def enterSubscript(self, ctx:JSONPathParser.SubscriptContext):
40 | pass
41 |
42 | # Exit a parse tree produced by JSONPathParser#subscript.
43 | def exitSubscript(self, ctx:JSONPathParser.SubscriptContext):
44 | pass
45 |
46 |
47 | # Enter a parse tree produced by JSONPathParser#subscriptables.
48 | def enterSubscriptables(self, ctx:JSONPathParser.SubscriptablesContext):
49 | pass
50 |
51 | # Exit a parse tree produced by JSONPathParser#subscriptables.
52 | def exitSubscriptables(self, ctx:JSONPathParser.SubscriptablesContext):
53 | pass
54 |
55 |
56 | # Enter a parse tree produced by JSONPathParser#subscriptableArguments.
57 | def enterSubscriptableArguments(self, ctx:JSONPathParser.SubscriptableArgumentsContext):
58 | pass
59 |
60 | # Exit a parse tree produced by JSONPathParser#subscriptableArguments.
61 | def exitSubscriptableArguments(self, ctx:JSONPathParser.SubscriptableArgumentsContext):
62 | pass
63 |
64 |
65 | # Enter a parse tree produced by JSONPathParser#subscriptableBareword.
66 | def enterSubscriptableBareword(self, ctx:JSONPathParser.SubscriptableBarewordContext):
67 | pass
68 |
69 | # Exit a parse tree produced by JSONPathParser#subscriptableBareword.
70 | def exitSubscriptableBareword(self, ctx:JSONPathParser.SubscriptableBarewordContext):
71 | pass
72 |
73 |
74 | # Enter a parse tree produced by JSONPathParser#subscriptable.
75 | def enterSubscriptable(self, ctx:JSONPathParser.SubscriptableContext):
76 | pass
77 |
78 | # Exit a parse tree produced by JSONPathParser#subscriptable.
79 | def exitSubscriptable(self, ctx:JSONPathParser.SubscriptableContext):
80 | pass
81 |
82 |
83 | # Enter a parse tree produced by JSONPathParser#sliceable.
84 | def enterSliceable(self, ctx:JSONPathParser.SliceableContext):
85 | pass
86 |
87 | # Exit a parse tree produced by JSONPathParser#sliceable.
88 | def exitSliceable(self, ctx:JSONPathParser.SliceableContext):
89 | pass
90 |
91 |
92 | # Enter a parse tree produced by JSONPathParser#expression.
93 | def enterExpression(self, ctx:JSONPathParser.ExpressionContext):
94 | pass
95 |
96 | # Exit a parse tree produced by JSONPathParser#expression.
97 | def exitExpression(self, ctx:JSONPathParser.ExpressionContext):
98 | pass
99 |
100 |
101 | # Enter a parse tree produced by JSONPathParser#andExpression.
102 | def enterAndExpression(self, ctx:JSONPathParser.AndExpressionContext):
103 | pass
104 |
105 | # Exit a parse tree produced by JSONPathParser#andExpression.
106 | def exitAndExpression(self, ctx:JSONPathParser.AndExpressionContext):
107 | pass
108 |
109 |
110 | # Enter a parse tree produced by JSONPathParser#orExpression.
111 | def enterOrExpression(self, ctx:JSONPathParser.OrExpressionContext):
112 | pass
113 |
114 | # Exit a parse tree produced by JSONPathParser#orExpression.
115 | def exitOrExpression(self, ctx:JSONPathParser.OrExpressionContext):
116 | pass
117 |
118 |
119 | # Enter a parse tree produced by JSONPathParser#notExpression.
120 | def enterNotExpression(self, ctx:JSONPathParser.NotExpressionContext):
121 | pass
122 |
123 | # Exit a parse tree produced by JSONPathParser#notExpression.
124 | def exitNotExpression(self, ctx:JSONPathParser.NotExpressionContext):
125 | pass
126 |
127 |
128 | # Enter a parse tree produced by JSONPathParser#json.
129 | def enterJson(self, ctx:JSONPathParser.JsonContext):
130 | pass
131 |
132 | # Exit a parse tree produced by JSONPathParser#json.
133 | def exitJson(self, ctx:JSONPathParser.JsonContext):
134 | pass
135 |
136 |
137 | # Enter a parse tree produced by JSONPathParser#obj.
138 | def enterObj(self, ctx:JSONPathParser.ObjContext):
139 | pass
140 |
141 | # Exit a parse tree produced by JSONPathParser#obj.
142 | def exitObj(self, ctx:JSONPathParser.ObjContext):
143 | pass
144 |
145 |
146 | # Enter a parse tree produced by JSONPathParser#pair.
147 | def enterPair(self, ctx:JSONPathParser.PairContext):
148 | pass
149 |
150 | # Exit a parse tree produced by JSONPathParser#pair.
151 | def exitPair(self, ctx:JSONPathParser.PairContext):
152 | pass
153 |
154 |
155 | # Enter a parse tree produced by JSONPathParser#array.
156 | def enterArray(self, ctx:JSONPathParser.ArrayContext):
157 | pass
158 |
159 | # Exit a parse tree produced by JSONPathParser#array.
160 | def exitArray(self, ctx:JSONPathParser.ArrayContext):
161 | pass
162 |
163 |
164 | # Enter a parse tree produced by JSONPathParser#value.
165 | def enterValue(self, ctx:JSONPathParser.ValueContext):
166 | pass
167 |
168 | # Exit a parse tree produced by JSONPathParser#value.
169 | def exitValue(self, ctx:JSONPathParser.ValueContext):
170 | pass
171 |
172 |
173 |
174 | del JSONPathParser
--------------------------------------------------------------------------------
/jsonpath2/parser/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """The jsonpath parser module."""
4 | import antlr4
5 | from jsonpath2.expressions.operator import (
6 | AndVariadicOperatorExpression,
7 | EqualBinaryOperatorExpression,
8 | GreaterThanBinaryOperatorExpression,
9 | GreaterThanOrEqualToBinaryOperatorExpression,
10 | ContainsBinaryOperatorExpression,
11 | LessThanBinaryOperatorExpression,
12 | LessThanOrEqualToBinaryOperatorExpression,
13 | NotEqualBinaryOperatorExpression,
14 | NotUnaryOperatorExpression,
15 | OrVariadicOperatorExpression,
16 | )
17 | from jsonpath2.expressions.some import SomeExpression
18 | from jsonpath2.nodes.current import CurrentNode
19 | from jsonpath2.nodes.recursivedescent import RecursiveDescentNode
20 | from jsonpath2.nodes.root import RootNode
21 | from jsonpath2.nodes.subscript import SubscriptNode
22 | from jsonpath2.nodes.terminal import TerminalNode
23 |
24 | from jsonpath2.parser.JSONPathLexer import JSONPathLexer
25 | from jsonpath2.parser.JSONPathListener import JSONPathListener
26 | from jsonpath2.parser.JSONPathParser import JSONPathParser
27 |
28 | from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript
29 | from jsonpath2.subscripts.arrayslice import ArraySliceSubscript
30 | from jsonpath2.subscripts.callable import CallableSubscript
31 | from jsonpath2.subscripts.filter import FilterSubscript
32 | from jsonpath2.subscripts.node import NodeSubscript
33 | from jsonpath2.subscripts.objectindex import ObjectIndexSubscript
34 | from jsonpath2.subscripts.wildcard import WildcardSubscript
35 |
36 |
37 | class CallableSubscriptNotFoundError(ValueError):
38 | """Callable subscript not found error."""
39 |
40 | def __init__(self, name):
41 | """Initialize callable subscript not found error."""
42 | super(CallableSubscriptNotFoundError, self).__init__()
43 | self.name = name
44 |
45 | def __str__(self):
46 | """Message."""
47 | return "callable subscript '{0}' not found".format(
48 | self.name.replace("'", "\\'")
49 | )
50 |
51 |
52 | CALLABLE_SUBSCRIPTS_ = {cls.__str__: cls for cls in CallableSubscript.__subclasses__()}
53 |
54 |
55 | # pylint: disable=invalid-name
56 | def _createCallableSubscript(name, *args, **kwargs):
57 | """Create callable subscript for name, arguments and keyword arguments."""
58 | if name in CALLABLE_SUBSCRIPTS_:
59 | cls = CALLABLE_SUBSCRIPTS_[name]
60 | return cls(*args, **kwargs)
61 | raise CallableSubscriptNotFoundError(name)
62 |
63 |
64 | # pylint: enable=invalid-name
65 |
66 |
67 | class _ConsoleErrorListener(antlr4.error.ErrorListener.ConsoleErrorListener):
68 | # pylint: disable=too-many-arguments
69 | # this is an error handling issue with antlr
70 | def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
71 | raise ValueError("line {}:{} {}".format(line, column, msg))
72 |
73 | # pylint: enable=too-many-arguments
74 |
75 |
76 | class _JSONPathListener(JSONPathListener):
77 | def __init__(self, _stack=None):
78 | super(_JSONPathListener, self).__init__()
79 | self._stack = _stack if _stack else []
80 |
81 | def exitJsonpath(self, ctx: JSONPathParser.JsonpathContext):
82 | if ctx.getToken(JSONPathParser.ROOT_VALUE, 0) is not None:
83 | if bool(ctx.subscript()):
84 | next_node = self._stack.pop()
85 | else:
86 | next_node = TerminalNode()
87 | self._stack.append(RootNode(next_node))
88 | else:
89 | # NOTE Unreachable when listener is used as tree walker.
90 | raise ValueError() # pragma: no cover
91 |
92 | def exitJsonpath_(self, ctx: JSONPathParser.JsonpathContext):
93 | if bool(ctx.subscript()):
94 | next_node = self._stack.pop()
95 | else:
96 | next_node = TerminalNode()
97 |
98 | if ctx.getToken(JSONPathParser.ROOT_VALUE, 0) is not None:
99 | self._stack.append(RootNode(next_node))
100 | elif ctx.getToken(JSONPathParser.CURRENT_VALUE, 0) is not None:
101 | self._stack.append(CurrentNode(next_node))
102 | else:
103 | # NOTE Unreachable when listener is used as tree walker.
104 | raise ValueError() # pragma: no cover
105 |
106 | def exitJsonpath__(self, ctx: JSONPathParser.JsonpathContext):
107 | pass
108 |
109 | # pylint: disable=too-many-branches
110 | # It would sure be nice if we had a case statement.
111 | def exitSubscript(self, ctx: JSONPathParser.SubscriptContext):
112 | if ctx.getToken(JSONPathParser.RECURSIVE_DESCENT, 0) is not None:
113 | if bool(ctx.subscript()):
114 | next_node = self._stack.pop()
115 | else:
116 | next_node = TerminalNode()
117 | if bool(ctx.subscriptableBareword()):
118 | subscriptable_nodes = [self._stack.pop()]
119 | elif bool(ctx.subscriptables()):
120 | subscriptable_nodes = self._stack.pop()
121 | else:
122 | # NOTE Unreachable when listener is used as tree walker.
123 | raise ValueError() # pragma: no cover
124 | self._stack.append(
125 | RecursiveDescentNode(SubscriptNode(next_node, subscriptable_nodes))
126 | )
127 | elif ctx.getToken(JSONPathParser.SUBSCRIPT, 0) is not None:
128 | if bool(ctx.subscript()):
129 | next_node = self._stack.pop()
130 | else:
131 | next_node = TerminalNode()
132 | if bool(ctx.subscriptableBareword()):
133 | subscriptable_nodes = [self._stack.pop()]
134 | else:
135 | # NOTE Unreachable when listener is used as tree walker.
136 | raise ValueError() # pragma: no cover
137 | self._stack.append(SubscriptNode(next_node, subscriptable_nodes))
138 | else:
139 | if bool(ctx.subscript()):
140 | next_node = self._stack.pop()
141 | else:
142 | next_node = TerminalNode()
143 | if bool(ctx.subscriptables()):
144 | subscriptable_nodes = self._stack.pop()
145 | else:
146 | # NOTE Unreachable when listener is used as tree walker.
147 | raise ValueError() # pragma: no cover
148 | self._stack.append(SubscriptNode(next_node, subscriptable_nodes))
149 |
150 | # pylint: enable=too-many-branches
151 |
152 | def exitSubscriptables(self, ctx: JSONPathParser.SubscriptablesContext):
153 | subscriptable_nodes = []
154 | for _subscriptable_ctx in ctx.getTypedRuleContexts(
155 | JSONPathParser.SubscriptableContext
156 | ):
157 | subscriptable_node = self._stack.pop()
158 | subscriptable_nodes.insert(0, subscriptable_node)
159 | self._stack.append(subscriptable_nodes)
160 |
161 | def exitSubscriptableArguments(
162 | self, ctx: JSONPathParser.SubscriptableArgumentsContext
163 | ):
164 | args = []
165 | for _arg_ctx in ctx.getTypedRuleContexts(JSONPathParser.Jsonpath__Context):
166 | arg = self._stack.pop()
167 | args.insert(0, arg)
168 | self._stack.append(args)
169 |
170 | def exitSubscriptableBareword(
171 | self, ctx: JSONPathParser.SubscriptableBarewordContext
172 | ):
173 | if bool(ctx.ID()):
174 | text = ctx.ID().getText()
175 |
176 | if bool(ctx.subscriptableArguments()):
177 | args = self._stack.pop()
178 |
179 | self._stack.append(_createCallableSubscript(text, *args))
180 | else:
181 | self._stack.append(ObjectIndexSubscript(text))
182 | elif ctx.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) is not None:
183 | self._stack.append(WildcardSubscript())
184 | else:
185 | # NOTE Unreachable when listener is used as tree walker.
186 | raise ValueError() # pragma: no cover
187 |
188 | def exitSubscriptable(self, ctx: JSONPathParser.SubscriptableContext):
189 | if bool(ctx.STRING()):
190 | text = ctx.STRING().getText()[1:-1]
191 |
192 | self._stack.append(ObjectIndexSubscript(text))
193 | elif bool(ctx.NUMBER()):
194 | if bool(ctx.sliceable()):
195 | func = self._stack.pop()
196 |
197 | start = int(ctx.NUMBER().getText()) if bool(ctx.NUMBER()) else None
198 |
199 | self._stack.append(func(start))
200 | else:
201 | index = int(ctx.NUMBER().getText())
202 |
203 | self._stack.append(ArrayIndexSubscript(index))
204 | elif bool(ctx.sliceable()):
205 | func = self._stack.pop()
206 |
207 | start = None
208 |
209 | self._stack.append(func(start))
210 | elif ctx.getToken(JSONPathParser.WILDCARD_SUBSCRIPT, 0) is not None:
211 | self._stack.append(WildcardSubscript())
212 | elif ctx.getToken(JSONPathParser.QUESTION, 0) is not None:
213 | expression = self._stack.pop()
214 |
215 | self._stack.append(FilterSubscript(expression))
216 | elif bool(ctx.jsonpath_()):
217 | next_node = self._stack.pop()
218 |
219 | self._stack.append(NodeSubscript(next_node))
220 | elif bool(ctx.ID()):
221 | text = ctx.ID().getText()
222 |
223 | args = self._stack.pop()
224 |
225 | self._stack.append(_createCallableSubscript(text, *args))
226 | else:
227 | # NOTE Unreachable when listener is used as tree walker.
228 | raise ValueError() # pragma: no cover
229 |
230 | def exitSliceable(self, ctx: JSONPathParser.SliceableContext):
231 | # NOTE https://github.com/pacifica/python-jsonpath2/issues/35
232 | #
233 | # This issue is caused by the way that ANTLR4 indexes terminals in a
234 | # context. In ANTLR4, when two or more terminals with the same name are
235 | # present in the same production, but only a subset of terminals are
236 | # parsed, the indexing does not reflect the position of the terminal
237 | # within the production.
238 | #
239 | # For #35, when the string "::n" is parsed, for a given "n", the context
240 | # contains only 1 "NUMBER" terminal, which is assigned the index 0,
241 | # where the index 1 would be expected (with the index 0 returning 'None'
242 | # or a similar sentinel value).
243 | #
244 | # The mitigation is to test for the presence of the second "COLON"
245 | # terminal and then to verify that it came before the second "NUMBER"
246 | # terminal.
247 |
248 | if bool(ctx.NUMBER(0)):
249 | if bool(ctx.COLON(1)):
250 | if bool(ctx.NUMBER(1)):
251 | # When there are 2 "COLON" and "NUMBER" terminals, assign to
252 | # 'end' and 'step' as usual.
253 |
254 | end = int(ctx.NUMBER(0).getText())
255 |
256 | step = int(ctx.NUMBER(1).getText())
257 | elif (
258 | ctx.NUMBER(0).getSourceInterval()[0]
259 | < ctx.COLON(1).getSourceInterval()[0]
260 | ):
261 | # When there are 2 "COLON" terminals but only 1 "NUMBER"
262 | # terminal, if the "NUMBER" terminal occurs **before** the
263 | # second "COLON" terminal, then assign to 'end' only.
264 |
265 | end = int(ctx.NUMBER(0).getText())
266 |
267 | step = None
268 | else:
269 | # When there are 2 "COLON" terminals but only 1 "NUMBER"
270 | # terminal, if the "NUMBER" terminal occurs **after** the
271 | # second "COLON" terminal, then assign to 'step' only.
272 |
273 | end = None
274 |
275 | step = int(ctx.NUMBER(0).getText())
276 | else:
277 | # When there is 1 "COLON" terminal and 1 "NUMBER" terminal,
278 | # assignn to 'end' only.
279 |
280 | end = int(ctx.NUMBER(0).getText())
281 |
282 | step = None
283 | else:
284 | end = None
285 |
286 | step = None
287 |
288 | self._stack.append(lambda start: ArraySliceSubscript(start, end, step))
289 |
290 | def exitAndExpression(self, ctx: JSONPathParser.AndExpressionContext):
291 | expressions = []
292 |
293 | if bool(ctx.andExpression()):
294 | expression = self._stack.pop()
295 |
296 | if isinstance(expression, AndVariadicOperatorExpression):
297 | expressions = expression.expressions + expressions
298 | else:
299 | expressions.insert(0, expression)
300 |
301 | expression = self._stack.pop()
302 |
303 | if isinstance(expression, AndVariadicOperatorExpression):
304 | expressions = expression.expressions + expressions
305 | else:
306 | expressions.insert(0, expression)
307 |
308 | if not expressions:
309 | # NOTE Unreachable when listener is used as tree walker.
310 | raise ValueError() # pragma: no cover
311 | if len(expressions) == 1:
312 | self._stack.append(expressions[0])
313 | else:
314 | self._stack.append(AndVariadicOperatorExpression(expressions))
315 |
316 | def exitOrExpression(self, ctx: JSONPathParser.OrExpressionContext):
317 | expressions = []
318 |
319 | if bool(ctx.orExpression()):
320 | expression = self._stack.pop()
321 |
322 | if isinstance(expression, OrVariadicOperatorExpression):
323 | expressions = expression.expressions + expressions
324 | else:
325 | expressions.insert(0, expression)
326 |
327 | expression = self._stack.pop()
328 |
329 | if isinstance(expression, OrVariadicOperatorExpression):
330 | expressions = expression.expressions + expressions
331 | else:
332 | expressions.insert(0, expression)
333 |
334 | if not expressions:
335 | # NOTE Unreachable when listener is used as tree walker.
336 | raise ValueError() # pragma: no cover
337 | if len(expressions) == 1:
338 | self._stack.append(expressions[0])
339 | else:
340 | self._stack.append(OrVariadicOperatorExpression(expressions))
341 |
342 | # pylint: disable=too-many-branches
343 | def exitNotExpression(self, ctx: JSONPathParser.NotExpressionContext):
344 | if bool(ctx.notExpression()):
345 | expression = self._stack.pop()
346 |
347 | if isinstance(expression, NotUnaryOperatorExpression):
348 | self._stack.append(expression.expression)
349 | else:
350 | self._stack.append(NotUnaryOperatorExpression(expression))
351 | elif bool(ctx.jsonpath__(1)):
352 | right_node_or_value = self._stack.pop()
353 |
354 | left_node_or_value = self._stack.pop()
355 |
356 | if ctx.getToken(JSONPathParser.EQ, 0) is not None:
357 | self._stack.append(
358 | EqualBinaryOperatorExpression(
359 | left_node_or_value, right_node_or_value
360 | )
361 | )
362 | elif ctx.getToken(JSONPathParser.NE, 0) is not None:
363 | self._stack.append(
364 | NotEqualBinaryOperatorExpression(
365 | left_node_or_value, right_node_or_value
366 | )
367 | )
368 | elif ctx.getToken(JSONPathParser.LT, 0) is not None:
369 | self._stack.append(
370 | LessThanBinaryOperatorExpression(
371 | left_node_or_value, right_node_or_value
372 | )
373 | )
374 | elif ctx.getToken(JSONPathParser.LE, 0) is not None:
375 | self._stack.append(
376 | LessThanOrEqualToBinaryOperatorExpression(
377 | left_node_or_value, right_node_or_value
378 | )
379 | )
380 | elif ctx.getToken(JSONPathParser.GT, 0) is not None:
381 | self._stack.append(
382 | GreaterThanBinaryOperatorExpression(
383 | left_node_or_value, right_node_or_value
384 | )
385 | )
386 | elif ctx.getToken(JSONPathParser.GE, 0) is not None:
387 | self._stack.append(
388 | GreaterThanOrEqualToBinaryOperatorExpression(
389 | left_node_or_value, right_node_or_value
390 | )
391 | )
392 | elif ctx.getToken(JSONPathParser.CN, 0) is not None:
393 | self._stack.append(
394 | ContainsBinaryOperatorExpression(
395 | left_node_or_value, right_node_or_value
396 | )
397 | )
398 | else:
399 | # NOTE Unreachable when listener is used as tree walker.
400 | raise ValueError() # pragma: no cover
401 | elif bool(ctx.jsonpath__(0)):
402 | next_node_or_value = self._stack.pop()
403 |
404 | self._stack.append(SomeExpression(next_node_or_value))
405 | else:
406 | pass
407 |
408 | # pylint: enable=too-many-branches
409 |
410 | def exitObj(self, ctx: JSONPathParser.ObjContext):
411 | values = []
412 |
413 | for pair_ctx in ctx.getTypedRuleContexts(JSONPathParser.PairContext):
414 | value = self._stack.pop()
415 |
416 | values.insert(0, value)
417 |
418 | obj = {}
419 |
420 | for index, pair_ctx in enumerate(
421 | ctx.getTypedRuleContexts(JSONPathParser.PairContext)
422 | ):
423 | key = pair_ctx.STRING().getText()[1:-1]
424 |
425 | obj[key] = values[index]
426 |
427 | self._stack.append(obj)
428 |
429 | def exitArray(self, ctx: JSONPathParser.ArrayContext):
430 | array = []
431 |
432 | for _value_ctx in ctx.getTypedRuleContexts(JSONPathParser.ValueContext):
433 | value = self._stack.pop()
434 |
435 | array.insert(0, value)
436 |
437 | self._stack.append(array)
438 |
439 | def exitValue(self, ctx: JSONPathParser.ValueContext):
440 | if bool(ctx.STRING()):
441 | text = ctx.STRING().getText()[1:-1]
442 |
443 | self._stack.append(text)
444 | elif bool(ctx.NUMBER()):
445 | text = ctx.NUMBER().getText()
446 |
447 | if ("." in text) or ("E" in text) or ("e" in text):
448 | self._stack.append(float(text))
449 | else:
450 | self._stack.append(int(text))
451 | elif bool(ctx.obj()):
452 | pass
453 | elif bool(ctx.array()):
454 | pass
455 | elif bool(ctx.TRUE()):
456 | self._stack.append(True)
457 | elif bool(ctx.FALSE()):
458 | self._stack.append(False)
459 | elif bool(ctx.NULL()):
460 | self._stack.append(None)
461 | else:
462 | # NOTE Unreachable when listener is used as tree walker.
463 | raise ValueError() # pragma: no cover
464 |
465 |
466 | class _JSONPathParser(JSONPathParser):
467 | # pylint: disable=invalid-name
468 | def tryCast(self, cls):
469 | """Override the antlr tryCast method."""
470 | try:
471 | cls(self._input.LT(-1).text)
472 | return True
473 | except ValueError:
474 | return False
475 |
476 | # pylint: enable=invalid-name
477 |
478 |
479 | def _parse_input_stream(input_stream: antlr4.InputStream) -> RootNode:
480 | error_listener = _ConsoleErrorListener()
481 |
482 | lexer = JSONPathLexer(input_stream)
483 |
484 | lexer.addErrorListener(error_listener)
485 |
486 | token_stream = antlr4.CommonTokenStream(lexer)
487 |
488 | parser = _JSONPathParser(token_stream)
489 |
490 | parser.addErrorListener(error_listener)
491 |
492 | tree = parser.jsonpath()
493 |
494 | listener = _JSONPathListener(_stack=[])
495 |
496 | walker = antlr4.ParseTreeWalker()
497 | walker.walk(listener, tree)
498 |
499 | # pylint: disable=protected-access
500 | return listener._stack.pop()
501 | # pylint: enable=protected-access
502 |
503 |
504 | def parse_file(*args, **kwargs) -> RootNode:
505 | """Parse a json path from a file."""
506 | file_stream = antlr4.FileStream(*args, **kwargs)
507 | return _parse_input_stream(file_stream)
508 |
509 |
510 | def parse_str(*args, **kwargs) -> RootNode:
511 | """Parse a json path from a string."""
512 | input_stream = antlr4.InputStream(*args, **kwargs)
513 | return _parse_input_stream(input_stream)
514 |
--------------------------------------------------------------------------------
/jsonpath2/path.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """The path module."""
4 | from typing import Generator
5 | from jsonpath2.node import MatchData
6 | from jsonpath2.nodes.root import RootNode
7 | import jsonpath2.parser as _parser
8 |
9 |
10 | class Path:
11 | """
12 | Path parsetree object.
13 |
14 | The ``jsonpath2.path.Path`` class represents a JSONPath.
15 |
16 | .. code-block:: python
17 |
18 | >>> s = '{"hello":"Hello, world!"}'
19 | '{"hello":"Hello, world!"}'
20 | >>> import json
21 | >>> d = json.loads(s)
22 | {'hello':'Hello, world!'}
23 | >>> from jsonpath2.path import Path
24 | >>> p = Path.parse_str('$["hello"]')
25 |
26 | >>> [match_data.current_value for match_data in p.match(d)]
27 | ['Hello, world!']
28 | >>> [match_data.node.tojsonpath() for match_data in p.match(d)]
29 | ['$["hello"]']
30 |
31 |
32 | This class is constructed with respect to the given instance of the
33 | ``jsonpath2.nodes.root.RootNode`` class (viz., the ``root_node`` property).
34 |
35 | Attributes:
36 | - ``root_node`` The root node of the abstract syntax tree for this instance.
37 | """
38 |
39 | def __init__(self, root_node: RootNode):
40 | """Constructor saving the root node."""
41 | super(Path, self).__init__()
42 | if isinstance(root_node, RootNode):
43 | self.root_node = root_node
44 | else:
45 | raise ValueError()
46 |
47 | def __eq__(self, other: object) -> bool:
48 | """
49 | Check to see if two paths are the same.
50 |
51 | Tests if two instances are equal.
52 | """
53 | return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__)
54 |
55 | def __str__(self) -> str:
56 | """
57 | Stringify the path object.
58 |
59 | Returns the string representation of this instance.
60 | """
61 | return self.root_node.tojsonpath()
62 |
63 | def match(self, root_value: object) -> Generator[MatchData, None, None]:
64 | """
65 | Match root value of the path.
66 |
67 | Match the given JSON data structure against this instance.
68 | For each match, yield an instance of the ``jsonpath2.node.MatchData`` class.
69 | """
70 | return self.root_node.match(root_value, root_value)
71 |
72 | @classmethod
73 | def parse_file(cls, *args, **kwargs):
74 | """
75 | A handler to parse a file.
76 |
77 | Parse the contents of the given file and return a new instance of this class.
78 | """
79 | return cls(_parser.parse_file(*args, **kwargs))
80 |
81 | @classmethod
82 | def parse_str(cls, *args, **kwargs):
83 | """
84 | A handler to parse a string.
85 |
86 | Parse the given string and return a new instance of this class.
87 | """
88 | return cls(_parser.parse_str(*args, **kwargs))
89 |
--------------------------------------------------------------------------------
/jsonpath2/subscript.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """The Subscript module."""
4 | from typing import Generator
5 | from jsonpath2.node import Node, MatchData
6 |
7 |
8 | class Subscript(Node):
9 | """Subscript has no value beyond a node other than type."""
10 |
11 | def __jsonpath__(
12 | self,
13 | ) -> Generator[str, None, None]: # pragma: no cover abstract method
14 | """Abstract method to return the jsonpath."""
15 |
16 | def match(
17 | self, root_value: object, current_value: object
18 | ) -> Generator[MatchData, None, None]: # pragma: no cover abstract method.
19 | """Abstract method to determine a node match."""
20 |
--------------------------------------------------------------------------------
/jsonpath2/subscripts/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Subscripts module contains the various subscripting classes."""
4 |
--------------------------------------------------------------------------------
/jsonpath2/subscripts/arrayindex.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Array Index subscript of the parse tree."""
4 | from collections.abc import Sequence
5 | import json
6 | from typing import Generator
7 | from jsonpath2.node import MatchData
8 | from jsonpath2.subscript import Subscript
9 | from jsonpath2.nodes.subscript import SubscriptNode
10 | from jsonpath2.nodes.terminal import TerminalNode
11 |
12 |
13 | class ArrayIndexSubscript(Subscript):
14 | """Array index subscript object."""
15 |
16 | def __init__(self, index: int):
17 | """Save the index of the subscript."""
18 | super(ArrayIndexSubscript, self).__init__()
19 | self.index = index
20 |
21 | def __jsonpath__(self) -> Generator[str, None, None]:
22 | """Dump the json index when rendering jsonpath."""
23 | yield json.dumps(self.index)
24 |
25 | def match(
26 | self, root_value: object, current_value: object
27 | ) -> Generator[MatchData, None, None]:
28 | """Match the root value against the current value."""
29 | if isinstance(current_value, Sequence) and not isinstance(current_value, str):
30 | if self.index < 0:
31 | new_index = self.index + len(current_value)
32 |
33 | if 0 <= new_index < len(current_value):
34 | return [
35 | MatchData(
36 | SubscriptNode(TerminalNode(), [self]),
37 | root_value,
38 | current_value[new_index],
39 | )
40 | ]
41 | elif self.index < len(current_value):
42 | return [
43 | MatchData(
44 | SubscriptNode(TerminalNode(), [self]),
45 | root_value,
46 | current_value[self.index],
47 | )
48 | ]
49 | return []
50 |
--------------------------------------------------------------------------------
/jsonpath2/subscripts/arrayslice.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Array slicing module."""
4 | from collections.abc import Sequence
5 | import itertools
6 | import json
7 | from typing import Generator
8 | from jsonpath2.node import MatchData
9 | from jsonpath2.subscript import Subscript
10 | from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript
11 |
12 |
13 | class ArraySliceSubscript(Subscript):
14 | """Array slice class for the parse tree."""
15 |
16 | def __init__(self, start: int = None, end: int = None, step: int = None):
17 | """Save the start end and step in the array slice."""
18 | super(ArraySliceSubscript, self).__init__()
19 | self.start = start
20 | self.end = end
21 | self.step = step
22 |
23 | def __jsonpath__(self) -> Generator[str, None, None]:
24 | """Return the jsonpath of the array slice."""
25 | if self.start is not None:
26 | yield json.dumps(self.start)
27 | yield ":"
28 | if self.end is not None:
29 | yield json.dumps(self.end)
30 | if self.step is not None:
31 | yield ":"
32 | yield json.dumps(self.step)
33 |
34 | def match(
35 | self, root_value: object, current_value: object
36 | ) -> Generator[MatchData, None, None]:
37 | """Match an array slice between values."""
38 | if isinstance(current_value, Sequence) and not isinstance(current_value, str):
39 | start = (
40 | None
41 | if (self.start is None)
42 | else (
43 | self.start
44 | + (
45 | (
46 | len(current_value)
47 | if abs(self.start) < len(current_value)
48 | else abs(self.start)
49 | )
50 | if (self.start < 0)
51 | else 0
52 | )
53 | )
54 | )
55 |
56 | end = (
57 | None
58 | if (self.end is None)
59 | else (self.end + (len(current_value) if (self.end < 0) else 0))
60 | )
61 |
62 | if start is None:
63 | if end is None:
64 | if self.step is None:
65 | indices = range(0, len(current_value))
66 | else:
67 | indices = range(0, len(current_value), self.step)
68 | else:
69 | if self.step is None:
70 | indices = range(0, end)
71 | else:
72 | indices = range(0, end, self.step)
73 | else:
74 | if end is None:
75 | if self.step is None:
76 | indices = range(start, len(current_value))
77 | else:
78 | indices = range(start, len(current_value), self.step)
79 | else:
80 | if self.step is None:
81 | indices = range(start, end)
82 | else:
83 | indices = range(start, end, self.step)
84 |
85 | return itertools.chain(
86 | *map(
87 | lambda index: ArrayIndexSubscript(index).match(
88 | root_value, current_value
89 | ),
90 | indices,
91 | )
92 | )
93 | return []
94 |
--------------------------------------------------------------------------------
/jsonpath2/subscripts/callable.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Callable subscript."""
4 | from collections.abc import Mapping, Sequence
5 | import itertools
6 | import json
7 | from typing import Generator, Tuple, Union
8 | from jsonpath2.node import MatchData, Node
9 | from jsonpath2.nodes.subscript import SubscriptNode
10 | from jsonpath2.nodes.terminal import TerminalNode
11 | from jsonpath2.subscript import Subscript
12 |
13 |
14 | class CallableSubscript(Subscript):
15 | """Callable subscript object."""
16 |
17 | def __init__(self, *args: Tuple[Union[MatchData, Node, object]]):
18 | """Initialize the callable subscript object."""
19 | super(CallableSubscript, self).__init__()
20 | self.args = args
21 |
22 | def __call__(
23 | self, root_value: object, current_value: object
24 | ) -> Generator[MatchData, None, None]: # pragma: no cover abstract method.
25 | """Call the callable subscript object."""
26 | raise NotImplementedError()
27 |
28 | def __jsonpath__(self) -> Generator[str, None, None]:
29 | """Generate the JSONPath for the callable subscript."""
30 | yield self.__class__.__str__
31 |
32 | yield "("
33 |
34 | for index, arg in enumerate(self.args):
35 | if index > 0:
36 | yield ","
37 |
38 | if isinstance(arg, MatchData):
39 | yield json.dumps(arg.current_value)
40 | elif isinstance(arg, Node):
41 | for arg_token in arg.__jsonpath__():
42 | yield arg_token
43 | else:
44 | yield json.dumps(arg)
45 |
46 | yield ")"
47 |
48 | # pylint: disable=line-too-long
49 | def match(
50 | self, root_value: object, current_value: object
51 | ) -> Generator[MatchData, None, None]:
52 | """Match the root value against the current value."""
53 | for args in itertools.product(
54 | *map(
55 | lambda arg: [arg]
56 | if isinstance(arg, MatchData)
57 | else arg.match(root_value, current_value)
58 | if isinstance(arg, Node)
59 | else [MatchData(TerminalNode(), root_value, arg)],
60 | self.args,
61 | )
62 | ): # noqa: E501
63 | for callable_subscript_match_data in self.__class__(*args)(
64 | root_value, current_value
65 | ):
66 | yield callable_subscript_match_data
67 |
68 | # pylint: enable=line-too-long
69 |
70 |
71 | class CharAtCallableSubscript(CallableSubscript):
72 | """charAt(int) callable subscript object."""
73 |
74 | __str__ = "charAt"
75 |
76 | def __call__(
77 | self, root_value: object, current_value: object
78 | ) -> Generator[MatchData, None, None]:
79 | """Perform charAt(number) call."""
80 | if isinstance(current_value, str):
81 | if (len(self.args) == 1) and isinstance(self.args[0].current_value, int):
82 | try:
83 | value = current_value[self.args[0].current_value]
84 | except IndexError:
85 | pass
86 | else:
87 | yield MatchData(
88 | SubscriptNode(TerminalNode(), [self]), root_value, value
89 | )
90 |
91 |
92 | class EntriesCallableSubscript(CallableSubscript):
93 | """entries() callable subscript object."""
94 |
95 | __str__ = "entries"
96 |
97 | def __call__(
98 | self, root_value: object, current_value: object
99 | ) -> Generator[MatchData, None, None]:
100 | """Perform entries() call."""
101 | if isinstance(current_value, Mapping):
102 | if not self.args:
103 | value = list(map(list, current_value.items()))
104 |
105 | yield MatchData(
106 | SubscriptNode(TerminalNode(), [self]), root_value, value
107 | )
108 | elif isinstance(current_value, Sequence) and not isinstance(current_value, str):
109 | if not self.args:
110 | value = list(map(list, enumerate(current_value)))
111 |
112 | yield MatchData(
113 | SubscriptNode(TerminalNode(), [self]), root_value, value
114 | )
115 |
116 |
117 | class KeysCallableSubscript(CallableSubscript):
118 | """keys() callable subscript object."""
119 |
120 | __str__ = "keys"
121 |
122 | def __call__(
123 | self, root_value: object, current_value: object
124 | ) -> Generator[MatchData, None, None]:
125 | """Perform keys() call."""
126 | if isinstance(current_value, Mapping):
127 | if not self.args:
128 | value = list(current_value.keys())
129 |
130 | yield MatchData(
131 | SubscriptNode(TerminalNode(), [self]), root_value, value
132 | )
133 | elif isinstance(current_value, Sequence) and not isinstance(current_value, str):
134 | if not self.args:
135 | value = list(range(len(current_value)))
136 |
137 | yield MatchData(
138 | SubscriptNode(TerminalNode(), [self]), root_value, value
139 | )
140 |
141 |
142 | class LengthCallableSubscript(CallableSubscript):
143 | """length() callable subscript object."""
144 |
145 | __str__ = "length"
146 |
147 | def __call__(
148 | self, root_value: object, current_value: object
149 | ) -> Generator[MatchData, None, None]:
150 | """Perform length() call."""
151 | if isinstance(current_value, Sequence) and not isinstance(current_value, str):
152 | if not self.args:
153 | value = len(current_value)
154 |
155 | yield MatchData(
156 | SubscriptNode(TerminalNode(), [self]), root_value, value
157 | )
158 | elif isinstance(current_value, str):
159 | if not self.args:
160 | value = len(current_value)
161 |
162 | yield MatchData(
163 | SubscriptNode(TerminalNode(), [self]), root_value, value
164 | )
165 |
166 |
167 | class SubstringCallableSubscript(CallableSubscript):
168 | """substring(int[, int]) callable subscript object."""
169 |
170 | __str__ = "substring"
171 |
172 | def __call__(
173 | self, root_value: object, current_value: object
174 | ) -> Generator[MatchData, None, None]:
175 | """Perform substring(number[, number]) call."""
176 | if isinstance(current_value, str):
177 | if (len(self.args) == 1) and isinstance(self.args[0].current_value, int):
178 | value = current_value[self.args[0].current_value :]
179 |
180 | yield MatchData(
181 | SubscriptNode(TerminalNode(), [self]), root_value, value
182 | )
183 | elif (
184 | (len(self.args) == 2)
185 | and isinstance(self.args[0].current_value, int)
186 | and isinstance(self.args[1].current_value, int)
187 | ):
188 | value = current_value[
189 | self.args[0].current_value : self.args[1].current_value
190 | ]
191 |
192 | yield MatchData(
193 | SubscriptNode(TerminalNode(), [self]), root_value, value
194 | )
195 |
196 |
197 | class ValuesCallableSubscript(CallableSubscript):
198 | """values() callable subscript object."""
199 |
200 | __str__ = "values"
201 |
202 | def __call__(
203 | self, root_value: object, current_value: object
204 | ) -> Generator[MatchData, None, None]:
205 | """Perform values() call."""
206 | if isinstance(current_value, Mapping):
207 | if not self.args:
208 | value = list(current_value.values())
209 |
210 | yield MatchData(
211 | SubscriptNode(TerminalNode(), [self]), root_value, value
212 | )
213 | elif isinstance(current_value, Sequence) and not isinstance(current_value, str):
214 | if not self.args:
215 | value = current_value
216 |
217 | yield MatchData(
218 | SubscriptNode(TerminalNode(), [self]), root_value, value
219 | )
220 |
--------------------------------------------------------------------------------
/jsonpath2/subscripts/filter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Filter parse tree."""
4 | from typing import Generator
5 | from jsonpath2.expression import Expression
6 | from jsonpath2.node import MatchData
7 | from jsonpath2.subscript import Subscript
8 | from jsonpath2.nodes.terminal import TerminalNode
9 |
10 |
11 | class FilterSubscript(Subscript):
12 | """Filter subscript in the parse tree."""
13 |
14 | def __init__(self, expression: Expression):
15 | """Save the filter expression."""
16 | super(FilterSubscript, self).__init__()
17 | self.expression = expression
18 |
19 | def __jsonpath__(self) -> Generator[str, None, None]:
20 | """generate the jsonpath for the filter."""
21 | yield "?"
22 | yield "("
23 | for expression_token in self.expression.__jsonpath__():
24 | yield expression_token
25 | yield ")"
26 |
27 | def match(
28 | self, root_value: object, current_value: object
29 | ) -> Generator[MatchData, None, None]:
30 | """Match the filter subscript against the current value."""
31 | if self.expression.evaluate(root_value, current_value):
32 | return [MatchData(TerminalNode(), root_value, current_value)]
33 | return []
34 |
--------------------------------------------------------------------------------
/jsonpath2/subscripts/node.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Node."""
4 | from typing import Generator
5 | from jsonpath2.node import MatchData, Node
6 | from jsonpath2.subscript import Subscript
7 | from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript
8 | from jsonpath2.subscripts.objectindex import ObjectIndexSubscript
9 |
10 |
11 | class NodeSubscript(Subscript):
12 | """Node subscript in the parse tree."""
13 |
14 | def __init__(self, next_node: Node):
15 | """Save the node subscript."""
16 | super(NodeSubscript, self).__init__()
17 | self.next_node = next_node
18 |
19 | def __jsonpath__(self) -> Generator[str, None, None]:
20 | """generate the jsonpath for the path."""
21 | return self.next_node.__jsonpath__()
22 |
23 | def match(
24 | self, root_value: object, current_value: object
25 | ) -> Generator[MatchData, None, None]:
26 | """Match the node subscript against the current value."""
27 | for next_node_match_data in self.next_node.match(root_value, current_value):
28 | if isinstance(next_node_match_data.current_value, int):
29 | subscript = ArrayIndexSubscript(next_node_match_data.current_value)
30 |
31 | for subscript_match_data in subscript.match(root_value, current_value):
32 | yield subscript_match_data
33 | elif isinstance(next_node_match_data.current_value, str):
34 | subscript = ObjectIndexSubscript(next_node_match_data.current_value)
35 |
36 | for subscript_match_data in subscript.match(root_value, current_value):
37 | yield subscript_match_data
38 |
--------------------------------------------------------------------------------
/jsonpath2/subscripts/objectindex.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Object index subscript module."""
4 | from collections.abc import Mapping
5 | import json
6 | from typing import Generator
7 | from jsonpath2.node import MatchData
8 | from jsonpath2.subscript import Subscript
9 | from jsonpath2.nodes.subscript import SubscriptNode
10 | from jsonpath2.nodes.terminal import TerminalNode
11 |
12 |
13 | class ObjectIndexSubscript(Subscript):
14 | """Object index subscript part of the jsonpath parse tree."""
15 |
16 | def __init__(self, index: str):
17 | """Save the string index into the json object."""
18 | super(ObjectIndexSubscript, self).__init__()
19 | self.index = index
20 |
21 | def __jsonpath__(self) -> Generator[str, None, None]:
22 | """Yield the dumps of the index."""
23 | yield json.dumps(self.index)
24 |
25 | def match(
26 | self, root_value: object, current_value: object
27 | ) -> Generator[MatchData, None, None]:
28 | """Match the current value against the root value."""
29 | if isinstance(current_value, Mapping) and (self.index in current_value):
30 | return [
31 | MatchData(
32 | SubscriptNode(TerminalNode(), [self]),
33 | root_value,
34 | current_value[self.index],
35 | )
36 | ]
37 | return []
38 |
--------------------------------------------------------------------------------
/jsonpath2/subscripts/wildcard.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Wild cart subscript module."""
4 | from collections.abc import Mapping, Sequence
5 | import itertools
6 | from typing import Generator
7 | from jsonpath2.node import MatchData
8 | from jsonpath2.subscript import Subscript
9 | from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript
10 | from jsonpath2.subscripts.objectindex import ObjectIndexSubscript
11 |
12 |
13 | class WildcardSubscript(Subscript):
14 | """Wild card subscript part of the parse tree."""
15 |
16 | def __jsonpath__(self) -> Generator[str, None, None]:
17 | """Yield the '*' wild card character."""
18 | yield "*"
19 |
20 | def match(
21 | self, root_value: object, current_value: object
22 | ) -> Generator[MatchData, None, None]:
23 | """Match the root value against the current value."""
24 | if isinstance(current_value, Mapping):
25 | return itertools.chain(
26 | *map(
27 | lambda index: ObjectIndexSubscript(index).match(
28 | root_value, current_value
29 | ),
30 | current_value.keys(),
31 | )
32 | )
33 | if isinstance(current_value, Sequence) and not isinstance(current_value, str):
34 | return itertools.chain(
35 | *map(
36 | lambda index: ArrayIndexSubscript(index).match(
37 | root_value, current_value
38 | ),
39 | range(len(current_value)),
40 | )
41 | )
42 | return []
43 |
--------------------------------------------------------------------------------
/jsonpath2/tojsonpath.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """A JSONPath abstract class."""
4 | from abc import ABC, abstractmethod
5 | from typing import Generator
6 |
7 |
8 | class ToJSONPath(ABC):
9 | """Abstract class which calls internal method."""
10 |
11 | def tojsonpath(self) -> str:
12 | """
13 | Get the json path from self and return it.
14 |
15 | Returns the string representation of this instance.
16 | """
17 | return "".join(list(self.__jsonpath__()))
18 |
19 | @abstractmethod
20 | def __jsonpath__(
21 | self,
22 | ) -> Generator[str, None, None]: # pragma: no cover abstract method
23 | """
24 | Abstract method to return the jsonpath.
25 |
26 | Yields the lexer tokens for the string representation of this instance.
27 | """
28 | raise NotImplementedError()
29 |
--------------------------------------------------------------------------------
/readthedocs.yml:
--------------------------------------------------------------------------------
1 | build:
2 | image: latest
3 | python:
4 | version: 3.7
5 | pip_install: true
6 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Setup the python package for jsonpath2."""
4 | from os import path
5 | from setuptools import setup, find_packages
6 |
7 | setup(
8 | name="jsonpath2",
9 | use_scm_version=True,
10 | setup_requires=["setuptools_scm"],
11 | license="LGPLv3",
12 | url="https://github.com/pacifica/python-jsonpath2/",
13 | description="JSONPath implementation for Python",
14 | long_description=open(
15 | path.join(path.abspath(path.dirname(__file__)), "README.md"), encoding="utf-8"
16 | ).read(),
17 | long_description_content_type="text/markdown",
18 | author="Mark Borkum",
19 | author_email="mark.borkum@pnnl.gov",
20 | packages=find_packages(),
21 | install_requires=["antlr4-python3-runtime==4.10"],
22 | )
23 |
--------------------------------------------------------------------------------
/tests/arrayslice_subscript_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Test the arrayslice object."""
4 | from unittest import TestCase
5 | from jsonpath2.subscripts.arrayslice import ArraySliceSubscript
6 |
7 |
8 | class TestArraySliceSubscript(TestCase):
9 | """Test the arrayslice base class."""
10 |
11 | def setUp(self):
12 | """Setup the class."""
13 | self.root_value = None
14 | self.current_value = list(range(10))
15 |
16 | def test_arrayslice0(self):
17 | """Test the arrayslice with configuration 000 (base 2)."""
18 | subscript = ArraySliceSubscript(None, None, None)
19 | self.assertEqual(":", subscript.tojsonpath())
20 | self.assertEqual(
21 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
22 | list(
23 | map(
24 | lambda match_data: match_data.current_value,
25 | subscript.match(self.root_value, self.current_value),
26 | )
27 | ),
28 | )
29 |
30 | def test_arrayslice1(self):
31 | """Test the arrayslice with configuration 001 (base 2)."""
32 | subscript = ArraySliceSubscript(None, None, 2)
33 | self.assertEqual("::2", subscript.tojsonpath())
34 | self.assertEqual(
35 | [0, 2, 4, 6, 8],
36 | list(
37 | map(
38 | lambda match_data: match_data.current_value,
39 | subscript.match(self.root_value, self.current_value),
40 | )
41 | ),
42 | )
43 |
44 | def test_arrayslice2(self):
45 | """Test the arrayslice with configuration 010 (base 2)."""
46 | subscript = ArraySliceSubscript(None, 7, None)
47 | self.assertEqual(":7", subscript.tojsonpath())
48 | self.assertEqual(
49 | [0, 1, 2, 3, 4, 5, 6],
50 | list(
51 | map(
52 | lambda match_data: match_data.current_value,
53 | subscript.match(self.root_value, self.current_value),
54 | )
55 | ),
56 | )
57 |
58 | def test_arrayslice3(self):
59 | """Test the arrayslice with configuration 011 (base 2)."""
60 | subscript = ArraySliceSubscript(None, 7, 2)
61 | self.assertEqual(":7:2", subscript.tojsonpath())
62 | self.assertEqual(
63 | [0, 2, 4, 6],
64 | list(
65 | map(
66 | lambda match_data: match_data.current_value,
67 | subscript.match(self.root_value, self.current_value),
68 | )
69 | ),
70 | )
71 |
72 | def test_arrayslice4(self):
73 | """Test the arrayslice with configuration 100 (base 2)."""
74 | subscript = ArraySliceSubscript(5, None, None)
75 | self.assertEqual("5:", subscript.tojsonpath())
76 | self.assertEqual(
77 | [5, 6, 7, 8, 9],
78 | list(
79 | map(
80 | lambda match_data: match_data.current_value,
81 | subscript.match(self.root_value, self.current_value),
82 | )
83 | ),
84 | )
85 |
86 | def test_arrayslice5(self):
87 | """Test the arrayslice with configuration 101 (base 2)."""
88 | subscript = ArraySliceSubscript(5, None, 2)
89 | self.assertEqual("5::2", subscript.tojsonpath())
90 | self.assertEqual(
91 | [5, 7, 9],
92 | list(
93 | map(
94 | lambda match_data: match_data.current_value,
95 | subscript.match(self.root_value, self.current_value),
96 | )
97 | ),
98 | )
99 |
100 | def test_arrayslice6(self):
101 | """Test the arrayslice with configuration 110 (base 2)."""
102 | subscript = ArraySliceSubscript(5, 7, None)
103 | self.assertEqual("5:7", subscript.tojsonpath())
104 | self.assertEqual(
105 | [5, 6],
106 | list(
107 | map(
108 | lambda match_data: match_data.current_value,
109 | subscript.match(self.root_value, self.current_value),
110 | )
111 | ),
112 | )
113 |
114 | def test_arrayslice7(self):
115 | """Test the arrayslice with configuration 111 (base 2)."""
116 | subscript = ArraySliceSubscript(5, 7, 2)
117 | self.assertEqual("5:7:2", subscript.tojsonpath())
118 | self.assertEqual(
119 | [5],
120 | list(
121 | map(
122 | lambda match_data: match_data.current_value,
123 | subscript.match(self.root_value, self.current_value),
124 | )
125 | ),
126 | )
127 |
128 | def test_arrayslice_not_list(self):
129 | """Test the arrayslice with non-list input."""
130 | subscript = ArraySliceSubscript()
131 | root_value = None
132 | self.assertTrue(not isinstance(root_value, list))
133 | self.assertEqual([], list(subscript.match(root_value, root_value)))
134 |
135 | def test_arrayslice8(self):
136 | """Test the arrayslice with configuration 1000 (base 2)."""
137 | subscript = ArraySliceSubscript(-15, None, None)
138 | self.assertEqual("-15:", subscript.tojsonpath())
139 | self.assertEqual(
140 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
141 | list(
142 | map(
143 | lambda match_data: match_data.current_value,
144 | subscript.match(self.root_value, self.current_value),
145 | )
146 | ),
147 | )
148 |
149 | def test_arrayslice9(self):
150 | """Test the arrayslice with configuration 1001 (base 2)."""
151 | subscript = ArraySliceSubscript(None, -15, None)
152 | self.assertEqual(":-15", subscript.tojsonpath())
153 | self.assertEqual(
154 | [],
155 | list(
156 | map(
157 | lambda match_data: match_data.current_value,
158 | subscript.match(self.root_value, self.current_value),
159 | )
160 | ),
161 | )
162 |
--------------------------------------------------------------------------------
/tests/bookstore_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Test the jsonpath module."""
4 | from unittest import TestCase
5 | from jsonpath2.path import Path
6 |
7 |
8 | class TestBookStore(TestCase):
9 | """
10 | Test the bookstore from original jsonpath article.
11 |
12 | http://goessner.net/articles/JsonPath/
13 | """
14 |
15 | def setUp(self):
16 | """Setup the class."""
17 | self.root_value = {
18 | "store": {
19 | "book": [
20 | {
21 | "category": "reference",
22 | "author": "Nigel Rees",
23 | "title": "Sayings of the Century",
24 | "price": 8.95,
25 | },
26 | {
27 | "category": "fiction",
28 | "author": "Evelyn Waugh",
29 | "title": "Sword of Honour",
30 | "price": 12.99,
31 | },
32 | {
33 | "category": "fiction",
34 | "author": "Herman Melville",
35 | "title": "Moby Dick",
36 | "isbn": "0-553-21311-3",
37 | "price": 8.99,
38 | },
39 | {
40 | "category": "fiction",
41 | "author": "J. R. R. Tolkien",
42 | "title": "The Lord of the Rings",
43 | "isbn": "0-395-19395-8",
44 | "price": 22.99,
45 | },
46 | ],
47 | "bicycle": {"color": "red", "price": 19.95},
48 | }
49 | }
50 |
51 | def test_bookstore_examples_1(self):
52 | """
53 | Test the bookstore example 1.
54 |
55 | .. code-block:: python
56 |
57 | >>> expr = Path.parse_str('$.store.book[*].author')
58 | >>> expr.match(self.root_value)
59 | """
60 | expr = Path.parse_str("$.store.book[*].author")
61 | self.assertEqual(Path.parse_str(str(expr)), expr)
62 | matches = [x.current_value for x in expr.match(self.root_value)]
63 | for auth in [
64 | "Nigel Rees",
65 | "Evelyn Waugh",
66 | "Herman Melville",
67 | "J. R. R. Tolkien",
68 | ]:
69 | self.assertTrue(auth in matches)
70 |
71 | def test_bookstore_examples_2(self):
72 | """
73 | Test the bookstore example 2.
74 |
75 | .. code-block:: python
76 |
77 | >>> expr = Path.parse_str('$..author')
78 | >>> expr.match(self.root_value)
79 | """
80 | expr = Path.parse_str("$..author")
81 | self.assertEqual(Path.parse_str(str(expr)), expr)
82 | matches = [x.current_value for x in expr.match(self.root_value)]
83 | for auth in [
84 | "Nigel Rees",
85 | "Evelyn Waugh",
86 | "Herman Melville",
87 | "J. R. R. Tolkien",
88 | ]:
89 | self.assertTrue(auth in matches)
90 |
91 | def test_bookstore_examples_3(self):
92 | """
93 | Test the bookstore example 3.
94 |
95 | .. code-block:: python
96 |
97 | >>> expr = Path.parse_str('$.store.*')
98 | >>> expr.match(self.root_value)
99 | """
100 | expr = Path.parse_str("$.store.*")
101 | self.assertEqual(Path.parse_str(str(expr)), expr)
102 | matches = [x.current_value for x in expr.match(self.root_value)]
103 | self.assertTrue(isinstance(matches[0], list))
104 | self.assertTrue(isinstance(matches[1], dict))
105 | self.assertEqual(matches[0][0]["author"], "Nigel Rees")
106 | self.assertEqual(matches[1]["color"], "red")
107 |
108 | def test_bookstore_examples_4(self):
109 | """
110 | Test the bookstore example 4.
111 |
112 | .. code-block:: python
113 |
114 | >>> expr = Path.parse_str('$.store..price')
115 | >>> expr.match(self.root_value)
116 | """
117 | expr = Path.parse_str("$.store..price")
118 | self.assertEqual(Path.parse_str(str(expr)), expr)
119 | matches = [x.current_value for x in expr.match(self.root_value)]
120 | for price in [8.95, 12.99, 8.99, 22.99, 19.95]:
121 | self.assertTrue(price in matches)
122 |
123 | def test_bookstore_examples_5(self):
124 | """
125 | Test the bookstore example 5.
126 |
127 | .. code-block:: python
128 |
129 | >>> expr = Path.parse_str('$..book[2]')
130 | >>> expr.match(self.root_value)
131 | """
132 | expr = Path.parse_str("$..book[2]")
133 | self.assertEqual(Path.parse_str(str(expr)), expr)
134 | matches = [x.current_value for x in expr.match(self.root_value)]
135 | self.assertEqual(matches[0]["category"], "fiction")
136 | self.assertEqual(matches[0]["author"], "Herman Melville")
137 | self.assertEqual(matches[0]["title"], "Moby Dick")
138 | self.assertEqual(matches[0]["isbn"], "0-553-21311-3")
139 | self.assertEqual(matches[0]["price"], 8.99)
140 |
141 | def test_bookstore_examples_6(self):
142 | """
143 | Test the bookstore example 6.
144 |
145 | .. code-block:: python
146 |
147 | >>> expr = Path.parse_str('$..book[-1:]')
148 | >>> expr.match(self.root_value)
149 | >>> expr = Path.parse_str('$..book[-1]')
150 | >>> expr.match(self.root_value)
151 | >>> expr = Path.parse_str('$..book[3:4:1]')
152 | >>> expr.match(self.root_value)
153 | """
154 | for path in ["$..book[-1:]", "$..book[-1]", "$..book[3:4:1]"]:
155 | expr = Path.parse_str(path)
156 | self.assertEqual(Path.parse_str(str(expr)), expr)
157 | matches = [x.current_value for x in expr.match(self.root_value)]
158 | self.assertEqual(matches[0]["category"], "fiction")
159 | self.assertEqual(matches[0]["author"], "J. R. R. Tolkien")
160 | self.assertEqual(matches[0]["title"], "The Lord of the Rings")
161 | self.assertEqual(matches[0]["isbn"], "0-395-19395-8")
162 | self.assertEqual(matches[0]["price"], 22.99)
163 |
164 | def test_bookstore_examples_7(self):
165 | """
166 | Test the bookstore example 7.
167 |
168 | .. code-block:: python
169 |
170 | >>> expr = Path.parse_str('$..book[0,1]')
171 | >>> expr.match(self.root_value)
172 | >>> expr = Path.parse_str('$..book[:2]')
173 | >>> expr.match(self.root_value)
174 | >>> expr = Path.parse_str('$..book[:2:1]')
175 | >>> expr.match(self.root_value)
176 | """
177 | for path in ["$..book[0,1]", "$..book[:2]", "$..book[:2:1]"]:
178 | expr = Path.parse_str(path)
179 | self.assertEqual(Path.parse_str(str(expr)), expr)
180 | matches = [x.current_value for x in expr.match(self.root_value)]
181 | self.assertEqual(matches[0]["category"], "reference")
182 | self.assertEqual(matches[0]["author"], "Nigel Rees")
183 | self.assertEqual(matches[0]["title"], "Sayings of the Century")
184 | self.assertEqual(matches[0]["price"], 8.95)
185 | self.assertEqual(matches[1]["category"], "fiction")
186 | self.assertEqual(matches[1]["author"], "Evelyn Waugh")
187 | self.assertEqual(matches[1]["title"], "Sword of Honour")
188 | self.assertEqual(matches[1]["price"], 12.99)
189 |
190 | def test_bookstore_examples_8(self):
191 | """
192 | Test the bookstore example 8.
193 |
194 | .. code-block:: python
195 |
196 | >>> expr = Path.parse_str('$..book[*][?(@.isbn)]')
197 | >>> expr.match(self.root_value)
198 | """
199 | expr = Path.parse_str("$..book[*][?(@.isbn)]")
200 | self.assertEqual(Path.parse_str(str(expr)), expr)
201 | matches = [x.current_value for x in expr.match(self.root_value)]
202 | self.assertEqual(matches[0]["category"], "fiction")
203 | self.assertEqual(matches[0]["author"], "Herman Melville")
204 | self.assertEqual(matches[0]["title"], "Moby Dick")
205 | self.assertEqual(matches[0]["isbn"], "0-553-21311-3")
206 | self.assertEqual(matches[0]["price"], 8.99)
207 | self.assertEqual(matches[1]["category"], "fiction")
208 | self.assertEqual(matches[1]["author"], "J. R. R. Tolkien")
209 | self.assertEqual(matches[1]["title"], "The Lord of the Rings")
210 | self.assertEqual(matches[1]["isbn"], "0-395-19395-8")
211 | self.assertEqual(matches[1]["price"], 22.99)
212 |
213 | def test_bookstore_examples_9(self):
214 | """
215 | Test the bookstore example 9.
216 |
217 | .. code-block:: python
218 |
219 | >>> expr = Path.parse_str('$..book[*][?(@.price<=10)]')
220 | >>> expr.match(self.root_value)
221 | >>> expr = Path.parse_str('$..book[*][?(@.price<10)]')
222 | >>> expr.match(self.root_value)
223 | """
224 | for path in ["$..book[*][?(@.price<10)]", "$..book[*][?(@.price<=10)]"]:
225 | expr = Path.parse_str(path)
226 | self.assertEqual(Path.parse_str(str(expr)), expr)
227 | matches = [x.current_value for x in expr.match(self.root_value)]
228 | self.assertEqual(matches[0]["category"], "reference")
229 | self.assertEqual(matches[0]["author"], "Nigel Rees")
230 | self.assertEqual(matches[0]["title"], "Sayings of the Century")
231 | self.assertEqual(matches[0]["price"], 8.95)
232 | self.assertEqual(matches[1]["category"], "fiction")
233 | self.assertEqual(matches[1]["author"], "Herman Melville")
234 | self.assertEqual(matches[1]["title"], "Moby Dick")
235 | self.assertEqual(matches[1]["isbn"], "0-553-21311-3")
236 | self.assertEqual(matches[1]["price"], 8.99)
237 |
238 | def test_bookstore_examples_10(self):
239 | """
240 | Test the bookstore example 10.
241 |
242 | .. code-block:: python
243 |
244 | >>> expr = Path.parse_str('$..book[*][?(@.author = "Nigel Rees")]')
245 | >>> expr.match(self.root_value)
246 | """
247 | expr = Path.parse_str('$..book[*][?(@.author = "Nigel Rees")]')
248 | self.assertEqual(Path.parse_str(str(expr)), expr)
249 | matches = [x.current_value for x in expr.match(self.root_value)]
250 | self.assertEqual(matches[0]["category"], "reference")
251 | self.assertEqual(matches[0]["author"], "Nigel Rees")
252 | self.assertEqual(matches[0]["title"], "Sayings of the Century")
253 | self.assertEqual(matches[0]["price"], 8.95)
254 |
255 | def test_bookstore_examples_11(self):
256 | """
257 | Test the bookstore example 11.
258 |
259 | .. code-block:: python
260 |
261 | >>> expr = Path.parse_str('$..book[*][?(@.category != "fiction")]')
262 | >>> expr.match(self.root_value)
263 | """
264 | expr = Path.parse_str('$..book[*][?(@.category != "fiction")]')
265 | self.assertEqual(Path.parse_str(str(expr)), expr)
266 | matches = [x.current_value for x in expr.match(self.root_value)]
267 | self.assertEqual(matches[0]["category"], "reference")
268 | self.assertEqual(matches[0]["author"], "Nigel Rees")
269 | self.assertEqual(matches[0]["title"], "Sayings of the Century")
270 | self.assertEqual(matches[0]["price"], 8.95)
271 |
272 | def test_bookstore_examples_12(self):
273 | """
274 | Test the bookstore example 12.
275 |
276 | .. code-block:: python
277 |
278 | >>> expr = Path.parse_str('$..book[*][?(@.price>=10)]')
279 | >>> expr.match(self.root_value)
280 | >>> expr = Path.parse_str('$..book[*][?(@.price>10)]')
281 | >>> expr.match(self.root_value)
282 | """
283 | for path in ["$..book[*][?(@.price>10)]", "$..book[*][?(@.price>=10)]"]:
284 | expr = Path.parse_str(path)
285 | self.assertEqual(Path.parse_str(str(expr)), expr)
286 | matches = [x.current_value for x in expr.match(self.root_value)]
287 | self.assertEqual(matches[0]["category"], "fiction")
288 | self.assertEqual(matches[0]["author"], "Evelyn Waugh")
289 | self.assertEqual(matches[0]["title"], "Sword of Honour")
290 | self.assertEqual(matches[0]["price"], 12.99)
291 | self.assertEqual(matches[1]["category"], "fiction")
292 | self.assertEqual(matches[1]["author"], "J. R. R. Tolkien")
293 | self.assertEqual(matches[1]["title"], "The Lord of the Rings")
294 | self.assertEqual(matches[1]["isbn"], "0-395-19395-8")
295 | self.assertEqual(matches[1]["price"], 22.99)
296 |
297 | def test_bookstore_examples_13(self):
298 | """
299 | Test the bookstore example 13.
300 |
301 | .. code-block:: python
302 |
303 | >>> expr = Path.parse_str('$..book[*][?(@.title contains "the")]')
304 | >>> expr.match(self.root_value)
305 | """
306 | expr = Path.parse_str('$..book[*][?(@.title contains "the")]')
307 | self.assertEqual(Path.parse_str(str(expr)), expr)
308 | matches = [x.current_value for x in expr.match(self.root_value)]
309 | self.assertEqual(len(matches), 2)
310 | self.assertEqual(matches[0]["category"], "reference")
311 | self.assertEqual(matches[0]["author"], "Nigel Rees")
312 | self.assertEqual(matches[0]["title"], "Sayings of the Century")
313 | self.assertEqual(matches[0]["price"], 8.95)
314 | self.assertEqual(matches[1]["title"], "The Lord of the Rings")
315 |
316 |
317 | class TestExtendedBookStore(TestCase):
318 | """This test extends the standard bookstore test for completness."""
319 |
320 | def setUp(self):
321 | """Copy the original bookstore document to this class."""
322 | orig_bookstore = TestBookStore()
323 | orig_bookstore.setUp()
324 | self.root_value = orig_bookstore.root_value
325 |
326 | def test_bookstore_extexample_1(self):
327 | """
328 | Test the bookstore example with step function.
329 |
330 | .. code-block:: python
331 |
332 | >>> expr = Path.parse_str('$..book[::2]')
333 | >>> expr.match(self.root_value)
334 | """
335 | expr = Path.parse_str("$..book[::2]")
336 | self.assertEqual(Path.parse_str(str(expr)), expr)
337 | matches = [x.current_value for x in expr.match(self.root_value)]
338 | self.assertEqual(matches[0]["category"], "reference")
339 | self.assertEqual(matches[0]["author"], "Nigel Rees")
340 | self.assertEqual(matches[0]["title"], "Sayings of the Century")
341 | self.assertEqual(matches[0]["price"], 8.95)
342 | self.assertEqual(matches[1]["category"], "fiction")
343 | self.assertEqual(matches[1]["author"], "Herman Melville")
344 | self.assertEqual(matches[1]["title"], "Moby Dick")
345 | self.assertEqual(matches[1]["isbn"], "0-553-21311-3")
346 | self.assertEqual(matches[1]["price"], 8.99)
347 |
348 | def test_bookstore_extexamples_2(self):
349 | """
350 | Test the bookstore example slice with end and multiple colons.
351 |
352 | .. code-block:: python
353 |
354 | >>> expr = Path.parse_str('$..book[:2:]')
355 | >>> expr.match(self.root_value)
356 | """
357 | expr = Path.parse_str("$..book[:2:]")
358 | self.assertEqual(Path.parse_str(str(expr)), expr)
359 | matches = [x.current_value for x in expr.match(self.root_value)]
360 | self.assertEqual(matches[0]["category"], "reference")
361 | self.assertEqual(matches[0]["author"], "Nigel Rees")
362 | self.assertEqual(matches[0]["title"], "Sayings of the Century")
363 | self.assertEqual(matches[0]["price"], 8.95)
364 | self.assertEqual(matches[1]["category"], "fiction")
365 | self.assertEqual(matches[1]["author"], "Evelyn Waugh")
366 | self.assertEqual(matches[1]["title"], "Sword of Honour")
367 | self.assertEqual(matches[1]["price"], 12.99)
368 |
369 | def test_bookstore_extexamples_3(self):
370 | """
371 | Test the bookstore example slice with start and multiple colons.
372 |
373 | .. code-block:: python
374 |
375 | >>> expr = Path.parse_str('$..book[2::]')
376 | >>> expr.match(self.root_value)
377 | """
378 | expr = Path.parse_str("$..book[2::]")
379 | self.assertEqual(Path.parse_str(str(expr)), expr)
380 | matches = [x.current_value for x in expr.match(self.root_value)]
381 | self.assertEqual(len(matches), 2)
382 | self.assertEqual(matches[0]["category"], "fiction")
383 | self.assertEqual(matches[0]["author"], "Herman Melville")
384 | self.assertEqual(matches[0]["title"], "Moby Dick")
385 | self.assertEqual(matches[0]["isbn"], "0-553-21311-3")
386 | self.assertEqual(matches[0]["price"], 8.99)
387 |
--------------------------------------------------------------------------------
/tests/expression_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Test the expression object."""
4 | from unittest import TestCase
5 | from jsonpath2.expressions.operator import (
6 | OperatorExpression,
7 | AndVariadicOperatorExpression,
8 | )
9 | from jsonpath2.expressions.some import SomeExpression
10 | from jsonpath2.nodes.current import CurrentNode
11 | from jsonpath2.nodes.terminal import TerminalNode
12 | from jsonpath2.path import Path
13 |
14 |
15 | class TestExpression(TestCase):
16 | """Test the expression base class."""
17 |
18 | def test_expression(self):
19 | """Test the base expression equal."""
20 | obj_a = OperatorExpression()
21 | obj_b = OperatorExpression()
22 | self.assertTrue(obj_a == obj_b)
23 |
24 | def test_failure_expressions(self):
25 | """Test expressions that make no sense."""
26 | data = {"hello": "Hello, World!"}
27 | for path in [
28 | '$[?(@.hello < "string")]',
29 | '$[?(@.hello <= "string")]',
30 | '$[?(@.hello > "string")]',
31 | '$[?(@.hello >= "string")]',
32 | '$[?(@.hello = {"bar": "baz"})]',
33 | '$[?(@.hello = ["bar", "baz"])]',
34 | "$[?(@.hello = 42)]",
35 | "$[?(@.hello = 3.14159)]",
36 | "$[?(@.hello = false)]",
37 | "$[?(@.hello = true)]",
38 | "$[?(@.hello = null)]",
39 | ]:
40 | print(path)
41 | expr = Path.parse_str(path)
42 | self.assertFalse(list(expr.match(data)))
43 |
44 | def test_unary_operator(self):
45 | """Test the unary not in a path."""
46 | data = [{"hello": "Hello, World!", "bar": False}]
47 | expr = Path.parse_str("$[?(not @.bar)]")
48 | self.assertEqual(Path.parse_str(str(expr)), expr)
49 | self.assertTrue(list(expr.match(data)))
50 |
51 | def test_unary_boolean_operator(self):
52 | """Test the unary not in a path."""
53 | data = [{"hello": "Hello, World!", "bar": False}]
54 | expr = Path.parse_str("$[?(not (@.bar or (@.fiz > 2 and @.biz > 2)))]")
55 | self.assertEqual(Path.parse_str(str(expr)), expr)
56 | self.assertTrue(list(expr.match(data)))
57 |
58 | def test_variadic_operator(self):
59 | """Test a variadic operator."""
60 | expr = AndVariadicOperatorExpression([])
61 | self.assertEqual("", expr.tojsonpath())
62 | expr = AndVariadicOperatorExpression(
63 | [SomeExpression(CurrentNode(TerminalNode()))]
64 | )
65 | self.assertEqual("@", expr.tojsonpath())
66 |
67 | def test_binary_operator(self):
68 | """Test a binary operator."""
69 | expr = Path.parse_str("$[?(@ and (@ and @))]")
70 | self.assertEqual("$[?(@ and @ and @)]", str(expr))
71 | expr = Path.parse_str("$[?(@ or (@ or @))]")
72 | self.assertEqual("$[?(@ or @ or @)]", str(expr))
73 | expr = Path.parse_str("$[?(not not @)]")
74 | self.assertEqual("$[?(@)]", str(expr))
75 | expr = Path.parse_str("$[?($ = null)]")
76 | self.assertEqual("$[?($ = null)]", str(expr))
77 | with self.assertRaises(ValueError):
78 | Path.parse_str("$[3.14156:]")
79 |
80 | # pylint: disable=invalid-name
81 | def test_binary_operator_with_path_subscript(self):
82 | """Test a binary operator with a path subscript."""
83 | data = {
84 | "foo": "bar",
85 | "fum": "baz",
86 | }
87 | expected_values = {
88 | '$[?(0)]["foo"]': [],
89 | '$[?(0 = 0)]["foo"]': ["bar"],
90 | '$[?(0 = @["fum"])]["foo"]': [],
91 | '$[?(@["foo"] = 0)]["foo"]': [],
92 | '$[?(@["foo"] = @["fum"])]["foo"]': [],
93 | }
94 | # pylint: disable=consider-iterating-dictionary
95 | for string in expected_values.keys():
96 | # pylint: enable=consider-iterating-dictionary
97 | expr = Path.parse_str(string)
98 | self.assertEqual(string, str(expr))
99 | values = [match_data.current_value for match_data in expr.match(data)]
100 | self.assertListEqual(expected_values.get(string), values)
101 |
102 | # pylint: enable=invalid-name
103 |
--------------------------------------------------------------------------------
/tests/jsonpath2_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Test the jsonpath module."""
4 | from json import loads
5 | from re import escape
6 | from tempfile import NamedTemporaryFile
7 | from unittest import TestCase
8 |
9 | from jsonpath2.node import MatchData
10 | from jsonpath2.expressions.some import SomeExpression
11 | from jsonpath2.nodes.current import CurrentNode
12 | from jsonpath2.nodes.recursivedescent import RecursiveDescentNode
13 | from jsonpath2.nodes.root import RootNode
14 | from jsonpath2.nodes.subscript import SubscriptNode
15 | from jsonpath2.nodes.terminal import TerminalNode
16 | from jsonpath2.path import Path
17 | from jsonpath2.subscripts.arrayindex import ArrayIndexSubscript
18 | from jsonpath2.subscripts.callable import (
19 | CharAtCallableSubscript,
20 | EntriesCallableSubscript,
21 | KeysCallableSubscript,
22 | LengthCallableSubscript,
23 | SubstringCallableSubscript,
24 | ValuesCallableSubscript,
25 | )
26 | from jsonpath2.subscripts.filter import FilterSubscript
27 | from jsonpath2.subscripts.objectindex import ObjectIndexSubscript
28 | from jsonpath2.subscripts.wildcard import WildcardSubscript
29 |
30 |
31 | class TestNode(TestCase):
32 | """Test the node object."""
33 |
34 | def setUp(self):
35 | """Setup the class."""
36 | root_value = {
37 | "hello": "Hello, world!",
38 | "languages": [
39 | "en-GB",
40 | "en-US",
41 | ],
42 | "preferences": {
43 | "coffee": True,
44 | "tea": False,
45 | },
46 | }
47 | current_value = root_value["hello"]
48 |
49 | self._state = [
50 | {
51 | "__jsonpath__": "",
52 | "node": TerminalNode(),
53 | "root_value": root_value,
54 | "current_value": current_value,
55 | "match_data_list": [
56 | MatchData(TerminalNode(), root_value, current_value),
57 | ],
58 | },
59 | {
60 | "__jsonpath__": "$",
61 | "node": RootNode(TerminalNode()),
62 | "root_value": root_value,
63 | "current_value": current_value,
64 | "match_data_list": [
65 | MatchData(RootNode(TerminalNode()), root_value, root_value),
66 | ],
67 | },
68 | {
69 | "__jsonpath__": "@",
70 | "node": CurrentNode(TerminalNode()),
71 | "root_value": root_value,
72 | "current_value": current_value,
73 | "match_data_list": [
74 | MatchData(CurrentNode(TerminalNode()), root_value, current_value),
75 | ],
76 | },
77 | {
78 | "__jsonpath__": "[]",
79 | "node": SubscriptNode(TerminalNode(), []),
80 | "root_value": root_value,
81 | "current_value": current_value,
82 | "match_data_list": [],
83 | },
84 | {
85 | "__jsonpath__": "[?(@)]",
86 | "node": SubscriptNode(
87 | TerminalNode(),
88 | [FilterSubscript(SomeExpression(CurrentNode(TerminalNode())))],
89 | ),
90 | "root_value": root_value,
91 | "current_value": current_value,
92 | "match_data_list": [
93 | MatchData(TerminalNode(), root_value, current_value),
94 | ],
95 | },
96 | {
97 | "__jsonpath__": "[?(@),?(@)]",
98 | "node": SubscriptNode(
99 | TerminalNode(),
100 | [
101 | FilterSubscript(SomeExpression(CurrentNode(TerminalNode()))),
102 | FilterSubscript(SomeExpression(CurrentNode(TerminalNode()))),
103 | ],
104 | ),
105 | "root_value": root_value,
106 | "current_value": current_value,
107 | "match_data_list": [
108 | MatchData(TerminalNode(), root_value, current_value),
109 | MatchData(TerminalNode(), root_value, current_value),
110 | ],
111 | },
112 | {
113 | "__jsonpath__": "[*]",
114 | "node": SubscriptNode(TerminalNode(), [WildcardSubscript()]),
115 | "root_value": root_value,
116 | "current_value": current_value,
117 | "match_data_list": [],
118 | },
119 | {
120 | "__jsonpath__": "[*]",
121 | "node": SubscriptNode(TerminalNode(), [WildcardSubscript()]),
122 | "root_value": root_value,
123 | "current_value": root_value,
124 | "match_data_list": [
125 | MatchData(
126 | SubscriptNode(TerminalNode(), [ObjectIndexSubscript("hello")]),
127 | root_value,
128 | root_value["hello"],
129 | ),
130 | MatchData(
131 | SubscriptNode(
132 | TerminalNode(), [ObjectIndexSubscript("languages")]
133 | ),
134 | root_value,
135 | root_value["languages"],
136 | ),
137 | MatchData(
138 | SubscriptNode(
139 | TerminalNode(), [ObjectIndexSubscript("preferences")]
140 | ),
141 | root_value,
142 | root_value["preferences"],
143 | ),
144 | ],
145 | },
146 | {
147 | "__jsonpath__": '["languages"][*]',
148 | "node": SubscriptNode(
149 | SubscriptNode(TerminalNode(), [WildcardSubscript()]),
150 | [ObjectIndexSubscript("languages")],
151 | ),
152 | "root_value": root_value,
153 | "current_value": root_value,
154 | "match_data_list": [
155 | MatchData(
156 | SubscriptNode(
157 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]),
158 | [ObjectIndexSubscript("languages")],
159 | ),
160 | root_value,
161 | root_value["languages"][0],
162 | ),
163 | MatchData(
164 | SubscriptNode(
165 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]),
166 | [ObjectIndexSubscript("languages")],
167 | ),
168 | root_value,
169 | root_value["languages"][1],
170 | ),
171 | ],
172 | },
173 | {
174 | "__jsonpath__": '["hello","languages"]',
175 | "node": SubscriptNode(
176 | TerminalNode(),
177 | [ObjectIndexSubscript("hello"), ObjectIndexSubscript("languages")],
178 | ),
179 | "root_value": root_value,
180 | "current_value": root_value,
181 | "match_data_list": [
182 | MatchData(
183 | SubscriptNode(TerminalNode(), [ObjectIndexSubscript("hello")]),
184 | root_value,
185 | root_value["hello"],
186 | ),
187 | MatchData(
188 | SubscriptNode(
189 | TerminalNode(), [ObjectIndexSubscript("languages")]
190 | ),
191 | root_value,
192 | root_value["languages"],
193 | ),
194 | ],
195 | },
196 | {
197 | "__jsonpath__": "..",
198 | "node": RecursiveDescentNode(TerminalNode()),
199 | "root_value": root_value,
200 | "current_value": current_value,
201 | "match_data_list": [
202 | MatchData(TerminalNode(), root_value, current_value),
203 | ],
204 | },
205 | {
206 | "__jsonpath__": "..",
207 | "node": RecursiveDescentNode(TerminalNode()),
208 | "root_value": root_value,
209 | "current_value": root_value,
210 | "match_data_list": [
211 | MatchData(TerminalNode(), root_value, root_value),
212 | MatchData(
213 | SubscriptNode(TerminalNode(), [ObjectIndexSubscript("hello")]),
214 | root_value,
215 | root_value["hello"],
216 | ),
217 | MatchData(
218 | SubscriptNode(
219 | TerminalNode(), [ObjectIndexSubscript("languages")]
220 | ),
221 | root_value,
222 | root_value["languages"],
223 | ),
224 | MatchData(
225 | SubscriptNode(
226 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]),
227 | [ObjectIndexSubscript("languages")],
228 | ),
229 | root_value,
230 | root_value["languages"][0],
231 | ),
232 | MatchData(
233 | SubscriptNode(
234 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]),
235 | [ObjectIndexSubscript("languages")],
236 | ),
237 | root_value,
238 | root_value["languages"][1],
239 | ),
240 | MatchData(
241 | SubscriptNode(
242 | TerminalNode(), [ObjectIndexSubscript("preferences")]
243 | ),
244 | root_value,
245 | root_value["preferences"],
246 | ),
247 | MatchData(
248 | SubscriptNode(
249 | SubscriptNode(
250 | TerminalNode(), [ObjectIndexSubscript("coffee")]
251 | ),
252 | [ObjectIndexSubscript("preferences")],
253 | ),
254 | root_value,
255 | root_value["preferences"]["coffee"],
256 | ),
257 | MatchData(
258 | SubscriptNode(
259 | SubscriptNode(
260 | TerminalNode(), [ObjectIndexSubscript("tea")]
261 | ),
262 | [ObjectIndexSubscript("preferences")],
263 | ),
264 | root_value,
265 | root_value["preferences"]["tea"],
266 | ),
267 | ],
268 | },
269 | {
270 | "__jsonpath__": "..[?(@)]",
271 | "node": RecursiveDescentNode(
272 | SubscriptNode(
273 | TerminalNode(),
274 | [FilterSubscript(SomeExpression(CurrentNode(TerminalNode())))],
275 | )
276 | ),
277 | "root_value": root_value,
278 | "current_value": root_value,
279 | "match_data_list": [
280 | MatchData(TerminalNode(), root_value, root_value),
281 | MatchData(
282 | SubscriptNode(TerminalNode(), [ObjectIndexSubscript("hello")]),
283 | root_value,
284 | root_value["hello"],
285 | ),
286 | MatchData(
287 | SubscriptNode(
288 | TerminalNode(), [ObjectIndexSubscript("languages")]
289 | ),
290 | root_value,
291 | root_value["languages"],
292 | ),
293 | MatchData(
294 | SubscriptNode(
295 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]),
296 | [ObjectIndexSubscript("languages")],
297 | ),
298 | root_value,
299 | root_value["languages"][0],
300 | ),
301 | MatchData(
302 | SubscriptNode(
303 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]),
304 | [ObjectIndexSubscript("languages")],
305 | ),
306 | root_value,
307 | root_value["languages"][1],
308 | ),
309 | MatchData(
310 | SubscriptNode(
311 | TerminalNode(), [ObjectIndexSubscript("preferences")]
312 | ),
313 | root_value,
314 | root_value["preferences"],
315 | ),
316 | MatchData(
317 | SubscriptNode(
318 | SubscriptNode(
319 | TerminalNode(), [ObjectIndexSubscript("coffee")]
320 | ),
321 | [ObjectIndexSubscript("preferences")],
322 | ),
323 | root_value,
324 | root_value["preferences"]["coffee"],
325 | ),
326 | MatchData(
327 | SubscriptNode(
328 | SubscriptNode(
329 | TerminalNode(), [ObjectIndexSubscript("tea")]
330 | ),
331 | [ObjectIndexSubscript("preferences")],
332 | ),
333 | root_value,
334 | root_value["preferences"]["tea"],
335 | ),
336 | ],
337 | },
338 | {
339 | "__jsonpath__": "..[*]",
340 | "node": RecursiveDescentNode(
341 | SubscriptNode(TerminalNode(), [WildcardSubscript()])
342 | ),
343 | "root_value": root_value,
344 | "current_value": root_value,
345 | "match_data_list": [
346 | MatchData(
347 | SubscriptNode(TerminalNode(), [ObjectIndexSubscript("hello")]),
348 | root_value,
349 | root_value["hello"],
350 | ),
351 | MatchData(
352 | SubscriptNode(
353 | TerminalNode(), [ObjectIndexSubscript("languages")]
354 | ),
355 | root_value,
356 | root_value["languages"],
357 | ),
358 | MatchData(
359 | SubscriptNode(
360 | TerminalNode(), [ObjectIndexSubscript("preferences")]
361 | ),
362 | root_value,
363 | root_value["preferences"],
364 | ),
365 | MatchData(
366 | SubscriptNode(
367 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]),
368 | [ObjectIndexSubscript("languages")],
369 | ),
370 | root_value,
371 | root_value["languages"][0],
372 | ),
373 | MatchData(
374 | SubscriptNode(
375 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(1)]),
376 | [ObjectIndexSubscript("languages")],
377 | ),
378 | root_value,
379 | root_value["languages"][1],
380 | ),
381 | MatchData(
382 | SubscriptNode(
383 | SubscriptNode(
384 | TerminalNode(), [ObjectIndexSubscript("coffee")]
385 | ),
386 | [ObjectIndexSubscript("preferences")],
387 | ),
388 | root_value,
389 | root_value["preferences"]["coffee"],
390 | ),
391 | MatchData(
392 | SubscriptNode(
393 | SubscriptNode(
394 | TerminalNode(), [ObjectIndexSubscript("tea")]
395 | ),
396 | [ObjectIndexSubscript("preferences")],
397 | ),
398 | root_value,
399 | root_value["preferences"]["tea"],
400 | ),
401 | ],
402 | },
403 | {
404 | "__jsonpath__": '..["hello"]',
405 | "node": RecursiveDescentNode(
406 | SubscriptNode(TerminalNode(), [ObjectIndexSubscript("hello")])
407 | ),
408 | "root_value": root_value,
409 | "current_value": root_value,
410 | "match_data_list": [
411 | MatchData(
412 | SubscriptNode(TerminalNode(), [ObjectIndexSubscript("hello")]),
413 | root_value,
414 | root_value["hello"],
415 | ),
416 | ],
417 | },
418 | {
419 | "__jsonpath__": "..[0]",
420 | "node": RecursiveDescentNode(
421 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)])
422 | ),
423 | "root_value": root_value,
424 | "current_value": root_value,
425 | "match_data_list": [
426 | MatchData(
427 | SubscriptNode(
428 | SubscriptNode(TerminalNode(), [ArrayIndexSubscript(0)]),
429 | [ObjectIndexSubscript("languages")],
430 | ),
431 | root_value,
432 | root_value["languages"][0],
433 | ),
434 | ],
435 | },
436 | {
437 | "__jsonpath__": '["hello"][length()]',
438 | # pylint: disable=line-too-long
439 | "node": SubscriptNode(
440 | SubscriptNode(TerminalNode(), [LengthCallableSubscript()]),
441 | [ObjectIndexSubscript("hello")],
442 | ), # noqa: E501
443 | # pylint: enable=line-too-long
444 | "root_value": root_value,
445 | "current_value": root_value,
446 | "match_data_list": [
447 | MatchData(
448 | SubscriptNode(
449 | SubscriptNode(TerminalNode(), [LengthCallableSubscript()]),
450 | [ObjectIndexSubscript("hello")],
451 | ),
452 | root_value,
453 | len(root_value["hello"]),
454 | ),
455 | ],
456 | },
457 | {
458 | "__jsonpath__": '["hello"][charAt(0)]',
459 | # pylint: disable=line-too-long
460 | "node": SubscriptNode(
461 | SubscriptNode(TerminalNode(), [CharAtCallableSubscript(0)]),
462 | [ObjectIndexSubscript("hello")],
463 | ), # noqa: E501
464 | # pylint: enable=line-too-long
465 | "root_value": root_value,
466 | "current_value": root_value,
467 | "match_data_list": [
468 | MatchData(
469 | SubscriptNode(
470 | SubscriptNode(
471 | TerminalNode(),
472 | [
473 | CharAtCallableSubscript(
474 | MatchData(TerminalNode(), root_value, 0)
475 | )
476 | ],
477 | ),
478 | [ObjectIndexSubscript("hello")],
479 | ),
480 | root_value,
481 | root_value["hello"][0],
482 | ),
483 | ],
484 | },
485 | {
486 | "__jsonpath__": '["hello"][charAt(1000)]',
487 | # pylint: disable=line-too-long
488 | "node": SubscriptNode(
489 | SubscriptNode(TerminalNode(), [CharAtCallableSubscript(1000)]),
490 | [ObjectIndexSubscript("hello")],
491 | ), # noqa: E501
492 | # pylint: enable=line-too-long
493 | "root_value": root_value,
494 | "current_value": root_value,
495 | "match_data_list": [],
496 | },
497 | {
498 | "__jsonpath__": '["hello"][charAt($["hello"][length()])]',
499 | "node": SubscriptNode(
500 | SubscriptNode(
501 | TerminalNode(),
502 | [
503 | CharAtCallableSubscript(
504 | RootNode(
505 | SubscriptNode(
506 | SubscriptNode(
507 | TerminalNode(), [LengthCallableSubscript()]
508 | ),
509 | [ObjectIndexSubscript("hello")],
510 | )
511 | )
512 | )
513 | ],
514 | ),
515 | [ObjectIndexSubscript("hello")],
516 | ),
517 | "root_value": root_value,
518 | "current_value": root_value,
519 | "match_data_list": [],
520 | },
521 | {
522 | "__jsonpath__": '["hello"][substring(1)]',
523 | # pylint: disable=line-too-long
524 | "node": SubscriptNode(
525 | SubscriptNode(TerminalNode(), [SubstringCallableSubscript(1)]),
526 | [ObjectIndexSubscript("hello")],
527 | ), # noqa: E501
528 | # pylint: enable=line-too-long
529 | "root_value": root_value,
530 | "current_value": root_value,
531 | "match_data_list": [
532 | MatchData(
533 | SubscriptNode(
534 | SubscriptNode(
535 | TerminalNode(),
536 | [
537 | SubstringCallableSubscript(
538 | MatchData(TerminalNode(), root_value, 1)
539 | )
540 | ],
541 | ),
542 | [ObjectIndexSubscript("hello")],
543 | ),
544 | root_value,
545 | root_value["hello"][1:],
546 | ),
547 | ],
548 | },
549 | {
550 | "__jsonpath__": '["hello"][substring(1,3)]',
551 | # pylint: disable=line-too-long
552 | "node": SubscriptNode(
553 | SubscriptNode(TerminalNode(), [SubstringCallableSubscript(1, 3)]),
554 | [ObjectIndexSubscript("hello")],
555 | ), # noqa: E501
556 | # pylint: enable=line-too-long
557 | "root_value": root_value,
558 | "current_value": root_value,
559 | "match_data_list": [
560 | MatchData(
561 | SubscriptNode(
562 | SubscriptNode(
563 | TerminalNode(),
564 | [
565 | SubstringCallableSubscript(
566 | MatchData(TerminalNode(), root_value, 1),
567 | MatchData(TerminalNode(), root_value, 3),
568 | )
569 | ],
570 | ),
571 | [ObjectIndexSubscript("hello")],
572 | ),
573 | root_value,
574 | root_value["hello"][1:3],
575 | ),
576 | ],
577 | },
578 | {
579 | "__jsonpath__": '["languages"][length()]',
580 | # pylint: disable=line-too-long
581 | "node": SubscriptNode(
582 | SubscriptNode(TerminalNode(), [LengthCallableSubscript()]),
583 | [ObjectIndexSubscript("languages")],
584 | ), # noqa: E501
585 | # pylint: enable=line-too-long
586 | "root_value": root_value,
587 | "current_value": root_value,
588 | "match_data_list": [
589 | MatchData(
590 | SubscriptNode(
591 | SubscriptNode(TerminalNode(), [LengthCallableSubscript()]),
592 | [ObjectIndexSubscript("languages")],
593 | ),
594 | root_value,
595 | len(root_value["languages"]),
596 | ),
597 | ],
598 | },
599 | {
600 | "__jsonpath__": '["languages"][entries()]',
601 | # pylint: disable=line-too-long
602 | "node": SubscriptNode(
603 | SubscriptNode(TerminalNode(), [EntriesCallableSubscript()]),
604 | [ObjectIndexSubscript("languages")],
605 | ), # noqa: E501
606 | # pylint: enable=line-too-long
607 | "root_value": root_value,
608 | "current_value": root_value,
609 | "match_data_list": [
610 | MatchData(
611 | SubscriptNode(
612 | SubscriptNode(TerminalNode(), [EntriesCallableSubscript()]),
613 | [ObjectIndexSubscript("languages")],
614 | ),
615 | root_value,
616 | list(map(list, enumerate(root_value["languages"]))),
617 | ),
618 | ],
619 | },
620 | {
621 | "__jsonpath__": '["languages"][keys()]',
622 | # pylint: disable=line-too-long
623 | "node": SubscriptNode(
624 | SubscriptNode(TerminalNode(), [KeysCallableSubscript()]),
625 | [ObjectIndexSubscript("languages")],
626 | ), # noqa: E501
627 | # pylint: enable=line-too-long
628 | "root_value": root_value,
629 | "current_value": root_value,
630 | "match_data_list": [
631 | MatchData(
632 | SubscriptNode(
633 | SubscriptNode(TerminalNode(), [KeysCallableSubscript()]),
634 | [ObjectIndexSubscript("languages")],
635 | ),
636 | root_value,
637 | list(range(len(root_value["languages"]))),
638 | ),
639 | ],
640 | },
641 | {
642 | "__jsonpath__": '["languages"][values()]',
643 | # pylint: disable=line-too-long
644 | "node": SubscriptNode(
645 | SubscriptNode(TerminalNode(), [ValuesCallableSubscript()]),
646 | [ObjectIndexSubscript("languages")],
647 | ), # noqa: E501
648 | # pylint: enable=line-too-long
649 | "root_value": root_value,
650 | "current_value": root_value,
651 | "match_data_list": [
652 | MatchData(
653 | SubscriptNode(
654 | SubscriptNode(TerminalNode(), [ValuesCallableSubscript()]),
655 | [ObjectIndexSubscript("languages")],
656 | ),
657 | root_value,
658 | root_value["languages"],
659 | ),
660 | ],
661 | },
662 | {
663 | "__jsonpath__": '["preferences"][entries()]',
664 | # pylint: disable=line-too-long
665 | "node": SubscriptNode(
666 | SubscriptNode(TerminalNode(), [EntriesCallableSubscript()]),
667 | [ObjectIndexSubscript("preferences")],
668 | ), # noqa: E501
669 | # pylint: enable=line-too-long
670 | "root_value": root_value,
671 | "current_value": root_value,
672 | "match_data_list": [
673 | MatchData(
674 | SubscriptNode(
675 | SubscriptNode(TerminalNode(), [EntriesCallableSubscript()]),
676 | [ObjectIndexSubscript("preferences")],
677 | ),
678 | root_value,
679 | list(map(list, root_value["preferences"].items())),
680 | ),
681 | ],
682 | },
683 | {
684 | "__jsonpath__": '["preferences"][keys()]',
685 | # pylint: disable=line-too-long
686 | "node": SubscriptNode(
687 | SubscriptNode(TerminalNode(), [KeysCallableSubscript()]),
688 | [ObjectIndexSubscript("preferences")],
689 | ), # noqa: E501
690 | # pylint: enable=line-too-long
691 | "root_value": root_value,
692 | "current_value": root_value,
693 | "match_data_list": [
694 | MatchData(
695 | SubscriptNode(
696 | SubscriptNode(TerminalNode(), [KeysCallableSubscript()]),
697 | [ObjectIndexSubscript("preferences")],
698 | ),
699 | root_value,
700 | list(root_value["preferences"].keys()),
701 | ),
702 | ],
703 | },
704 | {
705 | "__jsonpath__": '["preferences"][values()]',
706 | # pylint: disable=line-too-long
707 | "node": SubscriptNode(
708 | SubscriptNode(TerminalNode(), [ValuesCallableSubscript()]),
709 | [ObjectIndexSubscript("preferences")],
710 | ), # noqa: E501
711 | # pylint: enable=line-too-long
712 | "root_value": root_value,
713 | "current_value": root_value,
714 | "match_data_list": [
715 | MatchData(
716 | SubscriptNode(
717 | SubscriptNode(TerminalNode(), [ValuesCallableSubscript()]),
718 | [ObjectIndexSubscript("preferences")],
719 | ),
720 | root_value,
721 | list(root_value["preferences"].values()),
722 | ),
723 | ],
724 | },
725 | ]
726 |
727 | def _assert_node_test_case(self, **kwargs):
728 | self.assertEqual(kwargs["__jsonpath__"], kwargs["node"].tojsonpath())
729 |
730 | if isinstance(kwargs["node"], RootNode):
731 | self.assertEqual(
732 | kwargs["node"], Path.parse_str(kwargs["__jsonpath__"]).root_node
733 | )
734 |
735 | with NamedTemporaryFile() as temp_file:
736 | temp_file.write(bytes(kwargs["__jsonpath__"], "utf-8"))
737 | temp_file.seek(0)
738 |
739 | self.assertEqual(
740 | kwargs["node"], Path.parse_file(temp_file.name).root_node
741 | )
742 |
743 | else:
744 | with self.assertRaises(ValueError):
745 | Path(kwargs["node"])
746 |
747 | with self.assertRaises(ValueError):
748 | Path.parse_str("__jsonpath__")
749 |
750 | match_data_list = list(
751 | kwargs["node"].match(kwargs["root_value"], kwargs["current_value"])
752 | )
753 |
754 | self.assertListEqual(kwargs["match_data_list"], match_data_list)
755 | self.assertListEqual(
756 | list(
757 | map(
758 | lambda match_data: match_data.node.tojsonpath(),
759 | kwargs["match_data_list"],
760 | )
761 | ),
762 | list(map(lambda match_data: match_data.node.tojsonpath(), match_data_list)),
763 | )
764 |
765 | for match_data in match_data_list:
766 | new_match_data_list = list(
767 | match_data.node.match(kwargs["root_value"], kwargs["current_value"])
768 | )
769 |
770 | self.assertListEqual([match_data], new_match_data_list)
771 | self.assertListEqual(
772 | list(
773 | map(lambda match_data: match_data.node.tojsonpath(), [match_data])
774 | ),
775 | list(
776 | map(
777 | lambda match_data: match_data.node.tojsonpath(),
778 | new_match_data_list,
779 | )
780 | ),
781 | )
782 |
783 | def test_state(self):
784 | """Test the state."""
785 | for kwargs in self._state:
786 | self._assert_node_test_case(**kwargs)
787 |
788 | def test_example(self):
789 | """Test the example from the README.md."""
790 | test_string = '{"hello":"Hello, world!"}'
791 | test_json = loads(test_string)
792 | path_expr = Path.parse_str('$["hello"]')
793 | result = list(
794 | map(lambda match_data: match_data.current_value, path_expr.match(test_json))
795 | )
796 | self.assertEqual(result[0], "Hello, world!")
797 | result = list(
798 | map(
799 | lambda match_data: match_data.node.tojsonpath(),
800 | path_expr.match(test_json),
801 | )
802 | )
803 | self.assertEqual(result[0], '$["hello"]')
804 |
805 | # pylint: disable=invalid-name
806 | def test_parse_callable_subscript(self):
807 | """Test parse callable subscript."""
808 | Path.parse_str("$.length()")
809 | Path.parse_str("$[length()]")
810 | with self.assertRaisesRegex(
811 | ValueError, r"^" + escape("callable subscript 'foo' not found") + r"$"
812 | ):
813 | Path.parse_str("$.foo(1, 2, 3)")
814 |
815 | # pylint: enable=invalid-name
816 |
--------------------------------------------------------------------------------
/tests/node_subscript_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Test the jsonpath module."""
4 | from unittest import TestCase
5 |
6 | from jsonpath2.path import Path
7 |
8 |
9 | class TestNodeSubscript(TestCase):
10 | """Test the node subscript."""
11 |
12 | # pylint: disable=invalid-name
13 | def test_node_subscript(self):
14 | """Test the node subscript."""
15 | cases = [
16 | (
17 | [
18 | "foo",
19 | "bar",
20 | "fum",
21 | "baz",
22 | ],
23 | {
24 | '$["collection"][$["index"]]': [
25 | {
26 | "index": [],
27 | "expected_values": [],
28 | },
29 | {
30 | "index": 1,
31 | "expected_values": ["bar"],
32 | },
33 | ],
34 | '$["collection"][$["index"][*]]': [
35 | {
36 | "index": [1, 3],
37 | "expected_values": ["bar", "baz"],
38 | },
39 | ],
40 | },
41 | ),
42 | (
43 | {
44 | "foo": "bar",
45 | "fum": "baz",
46 | },
47 | {
48 | '$["collection"][$["index"]]': [
49 | {
50 | "index": [],
51 | "expected_values": [],
52 | },
53 | {
54 | "index": "foo",
55 | "expected_values": ["bar"],
56 | },
57 | ],
58 | '$["collection"][$["index"][*]]': [
59 | {
60 | "index": ["foo", "fum"],
61 | "expected_values": ["bar", "baz"],
62 | },
63 | ],
64 | },
65 | ),
66 | ]
67 | for collection, cases_for_collection in cases:
68 | for string, cases_for_string in cases_for_collection.items():
69 | for case_for_string in cases_for_string:
70 | path = Path.parse_str(string)
71 | self.assertEqual(string, str(path))
72 | matches = list(
73 | path.match(
74 | {
75 | "collection": collection,
76 | "index": case_for_string["index"],
77 | }
78 | )
79 | )
80 | self.assertListEqual(
81 | case_for_string["expected_values"],
82 | list(map(lambda match_data: match_data.current_value, matches)),
83 | )
84 |
85 | # pylint: enable=invalid-name
86 |
--------------------------------------------------------------------------------
/tests/shortcuts_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Test package level shortcuts."""
4 |
5 | from unittest import TestCase
6 | import jsonpath2
7 |
8 |
9 | class TestShortcuts(TestCase):
10 | """Test package level shortcuts."""
11 |
12 | def test_match(self):
13 | """Test match shortcut works."""
14 | data = {"hello": "Hello, World!"}
15 | self.assertEqual(
16 | [x.current_value for x in jsonpath2.match("$.hello", data)],
17 | ["Hello, World!"],
18 | )
19 |
--------------------------------------------------------------------------------
/tests/subscript_node_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """Test the subscript object."""
4 | from unittest import TestCase
5 | from jsonpath2.node import MatchData
6 | from jsonpath2.nodes.subscript import SubscriptNode
7 | from jsonpath2.nodes.terminal import TerminalNode
8 | from jsonpath2.subscript import Subscript
9 |
10 |
11 | class TestSubscriptNode(TestCase):
12 | """Test the subscript base class."""
13 |
14 | def test_badsubscript1(self):
15 | """Test subscript that does not provide terminal or other subscript."""
16 |
17 | class BadSubscript1(Subscript):
18 | """Subscript that does not provide terminal or other subscript."""
19 |
20 | def __jsonpath__(self):
21 | """Empty."""
22 | return []
23 |
24 | def match(self, root_value, current_value):
25 | """One."""
26 | yield MatchData(None, root_value, current_value)
27 |
28 | self.assertEqual("", BadSubscript1().tojsonpath())
29 |
30 | with self.assertRaises(ValueError):
31 | # NOTE Use 'list' to force the computation to occur, raising any exceptions.
32 | list(SubscriptNode(TerminalNode(), [BadSubscript1()]).match(None, None))
33 |
34 | def test_badsubscript2(self):
35 | """Test subscript that provides other subscript but not subscripted-terminal."""
36 |
37 | class BadSubscript2(Subscript):
38 | """Subscript that provides other subscript but not subscripted-terminal."""
39 |
40 | def __jsonpath__(self):
41 | """Empty."""
42 | return []
43 |
44 | def match(self, root_value, current_value):
45 | """One."""
46 | yield MatchData(SubscriptNode(None), root_value, current_value)
47 |
48 | self.assertEqual("", BadSubscript2().tojsonpath())
49 |
50 | with self.assertRaises(ValueError):
51 | # NOTE Use 'list' to force the computation to occur, raising any exceptions.
52 | list(SubscriptNode(TerminalNode(), [BadSubscript2()]).match(None, None))
53 |
--------------------------------------------------------------------------------