├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── docs ├── JsonValidator.html └── build_libdoc.py ├── requirements.txt ├── setup.cfg ├── setup.py └── src └── JsonValidator.py /.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 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 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | .idea/ 104 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | script: 8 | - python setup.py -q install -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | robotframework-jsonvalidator 2 | ============================ 3 | 4 | |Build Status| 5 | 6 | Short Description 7 | ----------------- 8 | 9 | `Robot Framework`_ library for JSON validation based on JSONSchema, 10 | JSONPath, JSONSelect. 11 | 12 | Additional Information 13 | ---------------------- 14 | 15 | - `Json Schema`_ 16 | - `Jsonschema generator`_ 17 | - `JSONPath by Stefan Goessner`_ 18 | - `JSONPath Tester`_ 19 | - `JSONSelect`_ 20 | - `JSONSelect Tester`_ 21 | 22 | Installation 23 | ------------ 24 | 25 | Install the library from PyPI using pip: 26 | 27 | :: 28 | 29 | pip install robotframework-jsonvalidator 30 | 31 | Dependencies 32 | ------------ 33 | 34 | - `jsonschema`_ 35 | - `jsonpath-rw-ext`_ 36 | - `objectpath`_ 37 | - `pyjsonselect`_ 38 | 39 | Documentation 40 | ------------- 41 | 42 | See keyword documentation for JsonValidator library on `GitHub`_. 43 | 44 | Example 45 | ------- 46 | 47 | *json_example.json* 48 | 49 | :: 50 | 51 | { 52 | "store": { 53 | "book": [ 54 | { 55 | "category": "reference", 56 | "author": "Nigel Rees", 57 | "title": "Sayings of the Century", 58 | "price": 8.95 59 | }, 60 | { 61 | "category": "fiction", 62 | "author": "Evelyn Waugh", 63 | "title": "Sword of Honour", 64 | "price": 12.99 65 | }, 66 | { 67 | "category": "fiction", 68 | "author": "Herman Melville", 69 | "title": "Moby Dick", 70 | "isbn": "0-553-21311-3", 71 | "price": 8.99 72 | }, 73 | { 74 | "category": "fiction", 75 | "author": "J. R. R. Tolkien", 76 | "title": "The Lord of the Rings", 77 | "isbn": "0-395-19395-8", 78 | "price": 22.99 79 | } 80 | ], 81 | "bicycle": { 82 | "color": "red", 83 | "price": 19.95 84 | } 85 | } 86 | } 87 | 88 | Robot Framework test case: 89 | 90 | .. code:: robotframework 91 | 92 | *** Settings *** 93 | Library JsonValidator 94 | Library OperatingSystem 95 | 96 | *** Test Cases *** 97 | Check Element 98 | ${json_example}= OperatingSystem.Get File ${CURDIR}${/}json_example.json 99 | Element should exist ${json_example} .author:contains("Evelyn Waugh") 100 | 101 | License 102 | ------- 103 | 104 | Apache License 2.0 105 | 106 | .. _Robot Framework: http://www.robotframework.org 107 | .. _Json Schema: http://json-schema.org/ 108 | .. _Jsonschema generator: http://www.jsonschema.net/ 109 | .. _JSONPath by Stefan Goessner: http://goessner.net/articles/JsonPath/ 110 | .. _JSONPath Tester: http://jsonpath.curiousconcept.com/ 111 | .. _JSONSelect: http://jsonselect.org/ 112 | .. _JSONSelect Tester: http://jsonselect.curiousconcept.com/ 113 | .. _jsonschema: https://pypi.python.org/pypi/jsonschema 114 | .. _jsonpath-rw-ext: https://pypi.python.org/pypi/jsonpath-rw-ext 115 | .. _objectpath: https://pypi.python.org/pypi/objectpath/ 116 | .. _pyjsonselect: https://pypi.python.org/pypi/pyjsonselect 117 | .. _GitHub: https://github.com/peterservice-rnd/robotframework-jsonvalidator/tree/master/docs 118 | 119 | .. |Build Status| image:: https://travis-ci.org/peterservice-rnd/robotframework-jsonvalidator.svg?branch=master 120 | :target: https://travis-ci.org/peterservice-rnd/robotframework-jsonvalidator 121 | -------------------------------------------------------------------------------- /docs/JsonValidator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 160 | 230 | 243 | 266 | 317 | 520 | 524 | 536 | 549 | 552 | 553 | 554 | 555 | 556 |
557 |

Opening library documentation failed

558 | 563 |
564 | 565 | 569 | 570 | 769 | 770 | 815 | 816 | 835 | 836 | 847 | 848 | 859 | 860 | 876 | 877 | 899 | 900 | 901 | 909 | 910 | 911 | 912 | -------------------------------------------------------------------------------- /docs/build_libdoc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Script to generate library documentation using module libdoc.""" 3 | 4 | from os.path import dirname, join, realpath 5 | from robot.libdoc import libdoc 6 | 7 | 8 | DOCS_DIR = dirname(__file__) 9 | SRC_DIR = realpath(join(DOCS_DIR, '..', 'src')) 10 | LIB_NAME = 'JsonValidator' 11 | 12 | if __name__ == '__main__': 13 | libdoc(join(SRC_DIR, LIB_NAME + '.py'), join(DOCS_DIR, LIB_NAME + '.html'), version='1.0.0') 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jsonpath-rw-ext>=1.0.0 2 | jsonschema>=2.5.1 3 | objectpath>=0.5 4 | pyjsonselect>=0.2.2 5 | robotframework>=3.0.1 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Setup module for Robot Framework JSON Validator Library""" 2 | 3 | from codecs import open 4 | from os import path 5 | from setuptools import setup 6 | 7 | here = path.abspath(path.dirname(__file__)) 8 | 9 | # Get the long description from the README file 10 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | # Get install requires from requirements.txt 14 | with open(path.join(here, 'requirements.txt')) as f: 15 | requirements = f.read().splitlines() 16 | 17 | setup( 18 | name='robotframework-jsonvalidator', 19 | version='2.0.0', 20 | description='A Robot Framework JSON Validator Library', 21 | long_description=long_description, 22 | url='https://github.com/peterservice-rnd/robotframework-jsonvalidator', 23 | license='Apache License, Version 2.0', 24 | author='JSC PETER-SERVICE', 25 | author_email='drse_aist_all@billing.ru', 26 | classifiers=[ 27 | 'Development Status :: 5 - Production/Stable', 28 | 'Intended Audience :: Developers', 29 | 'Topic :: Software Development :: Testing', 30 | 'License :: OSI Approved :: Apache Software License', 31 | 'Programming Language :: Python :: 3.5', 32 | 'Programming Language :: Python :: 3.6', 33 | 'Framework :: Robot Framework :: Library', 34 | ], 35 | keywords='testing robotframework json jsonschema jsonpath jsonselect', 36 | package_dir={'': 'src'}, 37 | py_modules=['JsonValidator'], 38 | install_requires=requirements, 39 | ) 40 | -------------------------------------------------------------------------------- /src/JsonValidator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import json 4 | import pprint 5 | from typing import Any, Dict, List, Optional, Union 6 | 7 | import jsonschema 8 | import jsonselect 9 | import objectpath 10 | from jsonpath_rw_ext import parse 11 | from jsonpath_rw.jsonpath import DatumInContext, Fields, Index, JSONPath 12 | 13 | JsonType = Union[Dict[str, Any], List[Dict[str, Any]]] # noqa: E993 14 | 15 | 16 | class JsonValidator(object): 17 | """ 18 | Library for JSON validation. 19 | Based on: JSONSchema, JSONPath, JSONSelect. 20 | 21 | == Additional Information == 22 | - [ http://json-schema.org/ | Json Schema ] 23 | - [ http://www.jsonschema.net/ | Jsonschema generator ] 24 | - [ http://goessner.net/articles/JsonPath/ | JSONPath by Stefan Goessner ] 25 | - [ http://jsonpath.curiousconcept.com/ | JSONPath Tester ] 26 | - [ http://jsonselect.org/ | JSONSelect] 27 | - [ http://jsonselect.curiousconcept.com/ | JSONSelect Tester] 28 | 29 | == Dependencies == 30 | | jsonschema | https://pypi.python.org/pypi/jsonschema | 31 | | jsonpath-rw-ext | https://pypi.python.org/pypi/jsonpath-rw-ext | 32 | | objectpath | https://pypi.python.org/pypi/objectpath/ | 33 | | pyjsonselect | https://pypi.python.org/pypi/pyjsonselect | 34 | 35 | == Example of use == 36 | json_example.json 37 | | { "store": { 38 | | "book": [ 39 | | { "category": "reference", 40 | | "author": "Nigel Rees", 41 | | "title": "Sayings of the Century", 42 | | "price": 8.95 43 | | }, 44 | | { "category": "fiction", 45 | | "author": "Evelyn Waugh", 46 | | "title": "Sword of Honour", 47 | | "price": 12.99 48 | | }, 49 | | { "category": "fiction", 50 | | "author": "Herman Melville", 51 | | "title": "Moby Dick", 52 | | "isbn": "0-553-21311-3", 53 | | "price": 8.99 54 | | }, 55 | | { "category": "fiction", 56 | | "author": "J. R. R. Tolkien", 57 | | "title": "The Lord of the Rings", 58 | | "isbn": "0-395-19395-8", 59 | | "price": 22.99 60 | | } 61 | | ], 62 | | "bicycle": { 63 | | "color": "red", 64 | | "price": 19.95 65 | | } 66 | | } 67 | | } 68 | 69 | | *Settings* | *Value* | 70 | | Library | JsonValidator | 71 | | Library | OperatingSystem | 72 | | *Test Cases* | *Action* | *Argument* | *Argument* | 73 | | Check element | ${json_example}= | OperatingSystem.Get File | ${CURDIR}${/}json_example.json | 74 | | | Element should exist | ${json_example} | .author:contains("Evelyn Waugh") | 75 | """ 76 | 77 | ROBOT_LIBRARY_SCOPE = 'GLOBAL' 78 | 79 | def __init__(self) -> None: 80 | """ Initialization. """ 81 | self._parser_cache: Dict[str, JSONPath] = {} 82 | 83 | def _parse(self, expr: str) -> JSONPath: 84 | """ 85 | Parse JSONPath expression and store it into the cache. 86 | 87 | *Args:*\n 88 | _expr_ - JSONPath expression; 89 | 90 | *Returns:*\n 91 | Parsed JSONPath expression. 92 | """ 93 | if expr not in self._parser_cache: 94 | self._parser_cache[expr] = parse(expr) 95 | return self._parser_cache[expr] 96 | 97 | def _validate_json(self, checked_json: JsonType, schema: Dict[str, Any]) -> None: 98 | """ Validate JSON according to JSONSchema 99 | 100 | *Args*:\n 101 | _checked_json_: validated JSON. 102 | _schema_: schema that used for validation. 103 | """ 104 | try: 105 | jsonschema.validate(checked_json, schema) 106 | except jsonschema.ValidationError as e: 107 | print("""Failed validating '{0}' 108 | in schema {1}: 109 | {2} 110 | On instance {3}: 111 | {4}""".format(e.validator, 112 | list(e.relative_schema_path)[:-1], pprint.pformat(e.schema), 113 | "[%s]" % "][".join(repr(index) for index in e.absolute_path), 114 | pprint.pformat(e.instance).encode('utf-8'))) 115 | raise JsonValidatorError("Failed validating json by schema") 116 | except jsonschema.SchemaError as e: 117 | raise JsonValidatorError(f'Json-schema error: {e}') 118 | 119 | def validate_jsonschema_from_file(self, json_source: Union[str, JsonType], path_to_schema: str) -> None: 120 | """ 121 | Validate JSON according to schema, loaded from a file. 122 | 123 | *Args:*\n 124 | _json_source_ - JSON data structure;\n 125 | _path_to_schema_ - path to file with JSON schema; 126 | 127 | *Raises:*\n 128 | JsonValidatorError 129 | 130 | *Example:*\n 131 | | *Settings* | *Value* | 132 | | Library | JsonValidator | 133 | | *Test Cases* | *Action* | *Argument* | *Argument* | 134 | | Simple | Validate jsonschema from file | {"foo":bar} | ${CURDIR}${/}schema.json | 135 | """ 136 | with open(path_to_schema) as f: 137 | schema = f.read() 138 | load_input_json = self.convert_to_json(json_source) 139 | 140 | try: 141 | load_schema = json.loads(schema) 142 | except ValueError as e: 143 | raise JsonValidatorError('Error in schema: {}'.format(e)) 144 | 145 | self._validate_json(load_input_json, load_schema) 146 | 147 | def validate_jsonschema(self, json_source: Union[str, JsonType], input_schema: str) -> None: 148 | """ 149 | Validate JSON according to schema. 150 | 151 | *Args:*\n 152 | _json_source_ - JSON data structure;\n 153 | _input_schema_ - schema in string format; 154 | 155 | *Raises:*\n 156 | JsonValidatorError 157 | 158 | *Example:*\n 159 | | *Settings* | *Value* | 160 | | Library | JsonValidator | 161 | | Library | OperatingSystem | 162 | | *Test Cases* | *Action* | *Argument* | *Argument* | 163 | | Simple | ${schema}= | OperatingSystem.Get File | ${CURDIR}${/}schema_valid.json | 164 | | | Validate jsonschema | {"foo":bar} | ${schema} | 165 | """ 166 | load_input_json = self.convert_to_json(json_source) 167 | 168 | try: 169 | load_schema = json.loads(input_schema) 170 | except ValueError as e: 171 | raise JsonValidatorError('Error in schema: {}'.format(e)) 172 | 173 | self._validate_json(load_input_json, load_schema) 174 | 175 | def convert_to_json(self, json_source: Union[str, JsonType]) -> JsonType: 176 | """Convert a python object to JsonType. 177 | 178 | Args: 179 | json_source: source object to convert. 180 | 181 | Returns: 182 | JSON structure. 183 | """ 184 | if isinstance(json_source, str): 185 | return self.string_to_json(json_source) 186 | elif isinstance(json_source, (dict, list)): 187 | return json_source 188 | else: 189 | raise JsonValidatorError(f'Invalid type of source_json: {type(json_source)}') 190 | 191 | def string_to_json(self, source: str) -> JsonType: 192 | """ 193 | Deserialize string into JSON structure. 194 | 195 | *Args:*\n 196 | _source_ - JSON string 197 | 198 | *Returns:*\n 199 | JSON structure 200 | 201 | *Raises:*\n 202 | JsonValidatorError 203 | 204 | *Example:*\n 205 | | *Settings* | *Value* | 206 | | Library | JsonValidator | 207 | | Library | OperatingSystem | 208 | | *Test Cases* | *Action* | *Argument* | *Argument* | 209 | | String to json | ${json_string}= | OperatingSystem.Get File | ${CURDIR}${/}json_example.json | 210 | | | ${json}= | String to json | ${json_string} | 211 | | | Log | ${json["store"]["book"][0]["price"]} | 212 | =>\n 213 | 8.95 214 | """ 215 | try: 216 | load_input_json = json.loads(source) 217 | except ValueError as e: 218 | raise JsonValidatorError(f"Could not parse '{source}' as JSON: {e}") 219 | return load_input_json 220 | 221 | def json_to_string(self, source: JsonType) -> str: 222 | """ 223 | Serialize JSON structure into string. 224 | 225 | *Args:*\n 226 | _source_ - JSON structure 227 | 228 | *Returns:*\n 229 | JSON string 230 | 231 | *Raises:*\n 232 | JsonValidatorError 233 | 234 | *Example:*\n 235 | | *Settings* | *Value* | 236 | | Library | JsonValidator | 237 | | Library | OperatingSystem | 238 | | *Test Cases* | *Action* | *Argument* | *Argument* | 239 | | Json to string | ${json_string}= | OperatingSystem.Get File | ${CURDIR}${/}json_example.json | 240 | | | ${json}= | String to json | ${json_string} | 241 | | | ${string}= | Json to string | ${json} | 242 | | | ${pretty_string}= | Pretty print json | ${string} | 243 | | | Log to console | ${pretty_string} | 244 | """ 245 | try: 246 | load_input_json = json.dumps(source) 247 | except ValueError as e: 248 | raise JsonValidatorError(f"Could serialize '{source}' to JSON: {e}") 249 | return load_input_json 250 | 251 | def get_elements(self, json_source: Union[str, JsonType], expr: str) -> Optional[List[Any]]: 252 | """ 253 | Get list of elements from _json_source_, matching [http://goessner.net/articles/JsonPath/|JSONPath] expression. 254 | 255 | *Args:*\n 256 | _json_source_ - JSON data structure;\n 257 | _expr_ - JSONPath expression; 258 | 259 | *Returns:*\n 260 | List of found elements or ``None`` if no elements were found 261 | 262 | *Example:*\n 263 | | *Settings* | *Value* | 264 | | Library | JsonValidator | 265 | | Library | OperatingSystem | 266 | | *Test Cases* | *Action* | *Argument* | *Argument* | 267 | | Get json elements | ${json_example}= | OperatingSystem.Get File | ${CURDIR}${/}json_example.json | 268 | | | ${json_elements}= | Get elements | ${json_example} | $.store.book[*].author | 269 | =>\n 270 | | [u'Nigel Rees', u'Evelyn Waugh', u'Herman Melville', u'J. R. R. Tolkien'] 271 | """ 272 | load_input_json = self.convert_to_json(json_source) 273 | # parsing jsonpath 274 | jsonpath_expr = self._parse(expr) 275 | # list of returned elements 276 | value_list = [] 277 | for match in jsonpath_expr.find(load_input_json): 278 | value_list.append(match.value) 279 | if not value_list: 280 | return None 281 | else: 282 | return value_list 283 | 284 | def select_elements(self, json_source: Union[str, JsonType], expr: str) -> Optional[List[Any]]: 285 | """ 286 | Return list of elements from _json_source_, matching [ http://jsonselect.org/ | JSONSelect] expression. 287 | 288 | *DEPRECATED* JSON Select query language is outdated and not supported any more. 289 | Use other keywords of this library to query JSON. 290 | 291 | *Args:*\n 292 | _json_source_ - JSON data structure;\n 293 | _expr_ - JSONSelect expression; 294 | 295 | *Returns:*\n 296 | List of found elements or ``None`` if no elements were found 297 | 298 | *Example:*\n 299 | | *Settings* | *Value* | 300 | | Library | JsonValidator | 301 | | Library | OperatingSystem | 302 | | *Test Cases* | *Action* | *Argument* | *Argument* | 303 | | Select json elements | ${json_example}= | OperatingSystem.Get File | ${CURDIR}${/}json_example.json | 304 | | | ${json_elements}= | Select elements | ${json_example} | .author:contains("Evelyn Waugh")~.price | 305 | =>\n 306 | | 12.99 307 | """ 308 | load_input_json = self.convert_to_json(json_source) 309 | # parsing jsonselect 310 | match = jsonselect.match(sel=expr, obj=load_input_json) 311 | ret = list(match) 312 | return ret if ret else None 313 | 314 | def select_objects(self, json_source: Union[str, JsonType], expr: str) -> Optional[List[Any]]: 315 | """ 316 | Return list of elements from _json_source_, matching [ http://objectpath.org// | ObjectPath] expression. 317 | 318 | *Args:*\n 319 | _json_source_ - JSON data structure;\n 320 | _expr_ - ObjectPath expression; 321 | 322 | *Returns:*\n 323 | List of found elements. If no elements were found, empty list will be returned 324 | 325 | *Example:*\n 326 | | *Settings* | *Value* | 327 | | Library | JsonValidator | 328 | | Library | OperatingSystem | 329 | | *Test Cases* | *Action* | *Argument* | *Argument* | 330 | | Select json objects | ${json_example}= | OperatingSystem.Get File | ${CURDIR}${/}json_example.json | 331 | | | ${json_objectss}= | Select objects | ${json_example} | $..book[@.author.name is "Evelyn Waugh"].price | 332 | =>\n 333 | | [12.99] 334 | """ 335 | load_input_json = self.convert_to_json(json_source) 336 | # parsing objectpath 337 | tree = objectpath.Tree(load_input_json) 338 | values = tree.execute(expr) 339 | return list(values) 340 | 341 | def element_should_exist(self, json_source: Union[str, JsonType], expr: str) -> None: 342 | """ 343 | Check the existence of one or more elements, matching [ http://jsonselect.org/ | JSONSelect] expression. 344 | 345 | *DEPRECATED* JSON Select query language is outdated and not supported any more. 346 | Use other keywords of this library to query JSON. 347 | 348 | *Args:*\n 349 | _json_source_ - JSON data structure;\n 350 | _expr_ - JSONSelect expression;\n 351 | 352 | *Raises:*\n 353 | JsonValidatorError 354 | 355 | *Example:*\n 356 | | *Settings* | *Value* | 357 | | Library | JsonValidator | 358 | | Library | OperatingSystem | 359 | | *Test Cases* | *Action* | *Argument* | *Argument* | 360 | | Check element | ${json_example}= | OperatingSystem.Get File | ${CURDIR}${/}json_example.json | 361 | | | Element should exist | ${json_example} | .author:contains("Evelyn Waugh") | 362 | | | Element should exist | ${json_example} | .store .book .price:expr(x=8.95) | 363 | """ 364 | value = self.select_elements(json_source, expr) 365 | if value is None: 366 | raise JsonValidatorError(f'Elements {expr} does not exist') 367 | 368 | def element_should_not_exist(self, json_source: Union[str, JsonType], expr: str) -> None: 369 | """ 370 | Check that one or more elements, matching [ http://jsonselect.org/ | JSONSelect] expression, don't exist. 371 | 372 | *DEPRECATED* JSON Select query language is outdated and not supported any more. 373 | Use other keywords of this library to query JSON. 374 | 375 | *Args:*\n 376 | _json_source_ - JSON data structure;\n 377 | _expr_ - JSONSelect expression;\n 378 | 379 | *Raises:*\n 380 | JsonValidatorError 381 | """ 382 | value = self.select_elements(json_source, expr) 383 | if value is not None: 384 | raise JsonValidatorError(f'Elements {expr} exist but should not') 385 | 386 | def _json_path_search(self, json_dict: JsonType, expr: str) -> List[DatumInContext]: 387 | """ 388 | Scan JSON dictionary with using json-path passed sting of the format of $.element..element1[index] etc. 389 | 390 | *Args:*\n 391 | _json_dict_ - JSON dictionary;\n 392 | _expr_ - string of fuzzy search for items within the directory;\n 393 | 394 | *Returns:*\n 395 | List of DatumInContext objects: 396 | ``[DatumInContext(value=..., path=..., context=[DatumInContext])]`` 397 | - value - found value 398 | - path - value selector inside context.value (in implementation of jsonpath-rw: class Index or Fields) 399 | 400 | *Raises:*\n 401 | JsonValidatorError 402 | """ 403 | path = self._parse(expr) 404 | results = path.find(json_dict) 405 | 406 | if len(results) == 0: 407 | raise JsonValidatorError(f"Nothing found in the dictionary {json_dict} using the given path {expr}") 408 | 409 | return results 410 | 411 | def update_json(self, json_source: Union[str, JsonType], expr: str, value: Any, 412 | index: Union[int, str] = 0) -> JsonType: 413 | """ 414 | Replace the value in the JSON structure. 415 | 416 | *Args:*\n 417 | _json_source_ - JSON data structure;\n 418 | _expr_ - JSONPath expression for determining the value to be replaced;\n 419 | _value_ - the value to be replaced with;\n 420 | _index_ - index for selecting item within a match list, default value is 0;\n 421 | 422 | *Returns:*\n 423 | Changed JSON in dictionary format. 424 | 425 | *Example:*\n 426 | | *Settings* | *Value* | 427 | | Library | JsonValidator | 428 | | Library | OperatingSystem | 429 | | *Test Cases* | *Action* | *Argument* | *Argument* | 430 | | Update element | ${json_example}= | OperatingSystem.Get File | ${CURDIR}${/}json_example.json | 431 | | | ${json_update}= | Update_json | ${json_example} | $..color | changed | 432 | """ 433 | load_input_json = self.convert_to_json(json_source) 434 | matches = self._json_path_search(load_input_json, expr) 435 | 436 | datum_object = matches[int(index)] 437 | 438 | if not isinstance(datum_object, DatumInContext): 439 | raise JsonValidatorError("Nothing found by the given json-path") 440 | 441 | path = datum_object.path 442 | 443 | # Edit the directory using the received data 444 | # If the user specified a list 445 | if isinstance(path, Index): 446 | datum_object.context.value[datum_object.path.index] = value 447 | # If the user specified a value of type (string, bool, integer or complex) 448 | elif isinstance(path, Fields): 449 | datum_object.context.value[datum_object.path.fields[0]] = value 450 | 451 | return load_input_json 452 | 453 | def pretty_print_json(self, json_string: str) -> str: 454 | """ 455 | Return formatted JSON string _json_string_.\n 456 | Using method json.dumps with settings: _indent=2, ensure_ascii=False_. 457 | 458 | *Args:*\n 459 | _json_string_ - JSON string. 460 | 461 | *Returns:*\n 462 | Formatted JSON string. 463 | 464 | *Example:*\n 465 | | *Settings* | *Value* | 466 | | Library | JsonValidator | 467 | | Library | OperatingSystem | 468 | | *Test Cases* | *Action* | *Argument* | *Argument* | 469 | | Check element | ${pretty_json}= | Pretty print json | {a:1,foo:[{b:2,c:3},{d:"baz",e:4}]} | 470 | | | Log | ${pretty_json} | 471 | =>\n 472 | | { 473 | | "a": 1, 474 | | "foo": [ 475 | | { 476 | | "c": 3, 477 | | "b": 2 478 | | }, 479 | | { 480 | | "e": 4, 481 | | "d": "baz" 482 | | } 483 | | ] 484 | | } 485 | """ 486 | return json.dumps(self.string_to_json(json_string), indent=2, ensure_ascii=False) 487 | 488 | 489 | class JsonValidatorError(Exception): 490 | pass 491 | --------------------------------------------------------------------------------