├── .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 | [![Build Status](https://travis-ci.org/pacifica/python-jsonpath2.svg?branch=master)](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 | --------------------------------------------------------------------------------