├── .github ├── dependabot.yml └── workflows │ ├── codeql.yaml │ └── maven.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── NOTICE ├── README.adoc ├── jsonassert-assertj-java8 ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── toomuchcoding │ │ └── jsonassert │ │ ├── BDDJsonAssertions.java │ │ ├── JsonAssertions.java │ │ └── JsonPathAssert.java │ └── test │ └── groovy │ └── com │ └── toomuchcoding │ └── jsonassert │ └── JsonAssertionWithAssertJJava8Spec.groovy ├── jsonassert-shade └── pom.xml ├── jsonassert ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── toomuchcoding │ │ └── jsonassert │ │ ├── ArrayAssertion.java │ │ ├── ArrayValueAssertion.java │ │ ├── FieldAssertion.java │ │ ├── IteratingOverArray.java │ │ ├── JsonAsserter.java │ │ ├── JsonAsserterConfiguration.java │ │ ├── JsonAssertion.java │ │ ├── JsonPath.java │ │ ├── JsonReader.java │ │ ├── JsonVerifiable.java │ │ ├── NamelessArrayHavingFieldAssertion.java │ │ └── ReadyToCheckAsserter.java │ └── test │ └── groovy │ └── com │ └── toomuchcoding │ └── jsonassert │ ├── JsonAssertionSpec.groovy │ └── JsonPathSpec.groovy ├── mvnw ├── mvnw.cmd ├── pom.xml ├── release.sh └── tests ├── pom.xml └── src └── test └── groovy └── shaded └── com └── toomuchcoding └── jsonassert ├── JsonAssertionSpec.groovy └── JsonPathSpec.groovy /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | target-branch: "main" 6 | schedule: 7 | interval: "weekly" 8 | - package-ecosystem: maven 9 | directory: / 10 | schedule: 11 | interval: daily 12 | target-branch: main 13 | ignore: 14 | # only upgrade by minor or patch 15 | - dependency-name: "*" 16 | update-types: 17 | - version-update:semver-major 18 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | name: "Code Scanning - Action" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | CodeQL-Build: 9 | # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest 10 | runs-on: ubuntu-latest 11 | 12 | permissions: 13 | # required for all workflows 14 | security-events: write 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | 20 | # Initializes the CodeQL tools for scanning. 21 | - name: Initialize CodeQL 22 | uses: github/codeql-action/init@v3 23 | # Override language selection by uncommenting this and choosing your languages 24 | # with: 25 | # languages: go, javascript, csharp, python, cpp, java 26 | 27 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 28 | # If this step fails, then you should remove it and run the build manually (see below). 29 | - name: Autobuild 30 | uses: github/codeql-action/autobuild@v3 31 | 32 | # ℹ️ Command-line programs to run using the OS shell. 33 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 34 | 35 | # ✏️ If the Autobuild fails above, remove it and uncomment the following 36 | # three lines and modify them (or add more) to build your code if your 37 | # project uses a compiled language 38 | 39 | #- run: | 40 | # make bootstrap 41 | # make release 42 | 43 | - name: Perform CodeQL Analysis 44 | uses: github/codeql-action/analyze@v3 45 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Build 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | java: ["8", "11", "15", "16", "17"] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Setup java 23 | uses: actions/setup-java@v4 24 | with: 25 | distribution: 'zulu' 26 | java-version: ${{ matrix.java }} 27 | - name: Cache local Maven repository 28 | uses: actions/cache@v4 29 | with: 30 | path: ~/.m2/repository 31 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 32 | restore-keys: | 33 | ${{ runner.os }}-maven- 34 | - name: Build with Maven 35 | run: ./mvnw clean install -B -U 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | #* 3 | *# 4 | .#* 5 | .factorypath 6 | .classpath 7 | .project 8 | .settings/ 9 | .sts4-cache/ 10 | target/ 11 | build/ 12 | bin/ 13 | .gradle/ 14 | 15 | *.class 16 | .attach* 17 | 18 | # Mobile Tools for Java (J2ME) 19 | .mtj.tmp/ 20 | 21 | # Package Files # 22 | *.war 23 | *.ear 24 | *.versionsBackup 25 | 26 | # virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml 27 | hs_err_pid* 28 | 29 | *.class 30 | *.iml 31 | 32 | .idea/ 33 | .gradle/ 34 | target/ 35 | build/ 36 | out/ 37 | 38 | hs_err_pid* 39 | .DS_Store 40 | *.log 41 | interpolated-settings.xml 42 | interpolated-pom.xml 43 | dependency-reduced-pom.xml 44 | 45 | .vscode/ 46 | .flattened-pom.xml 47 | .sdkmanrc 48 | release.properties 49 | pom.xml.next 50 | pom.xml.releaseBackup 51 | pom.xml.tag 52 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcingrzejszczak/jsonassert/d1d91249c3d9d9c664a6f9b00286235adaf6fd00/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | ========================================================================= 2 | == NOTICE file corresponding to the section 4 d of == 3 | == the Apache License, Version 2.0, == 4 | == in this case for the Spock distribution. == 5 | ========================================================================= 6 | 7 | This product includes software developed by 8 | The Apache Software Foundation (http://www.apache.org/). 9 | 10 | It includes the following other software: 11 | 12 | gentyref (http://code.google.com/p/gentyref/) 13 | 14 | For licenses see the LICENSE file. 15 | 16 | If any software distributed with Spock does not have an Apache 2 License, its license is explicitly listed in the 17 | LICENSE file. -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | image::https://github.com/marcingrzejszczak/jsonassert/workflows/Build/badge.svg?style=svg["Build",link="https://github.com/marcingrzejszczak/jsonassert/actions"] 2 | image::https://maven-badges.herokuapp.com/maven-central/com.toomuchcoding.jsonassert/jsonassert/badge.svg?style=plastic[Maven Central, link="https://maven-badges.herokuapp.com/maven-central/com.toomuchcoding.jsonassert/jsonassert"] 3 | image::https://badges.gitter.im/Join%20Chat.svg[Gitter, link="https://gitter.im/marcingrzejszczak/jsonassert?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] 4 | 5 | = JSON Assert 6 | 7 | Small library for those who have a hard time understanding the complexity of JSON Path. 8 | 9 | :toc: 10 | :toc-placement!: 11 | 12 | toc::[] 13 | 14 | = Rationale 15 | 16 | Have you ever met such a JSON Path expression? 17 | 18 | [source] 19 | ---- 20 | $[*].place.bounding_box.coordinates[*][*][?(@ == 38.791645)] 21 | ---- 22 | 23 | Pretty isn't it? Wouldn't it be better to just read: 24 | 25 | [source,java] 26 | ---- 27 | assertThat(jsonAsString).array().field("place").field("bounding_box").array("coordinates").array().contains(38.791645).value() 28 | ---- 29 | 30 | JSON Assert to the rescue! 31 | 32 | == Fast intro 33 | 34 | === Adding to your project 35 | 36 | .pom.xml (Maven) 37 | ```xml 38 | 39 | com.toomuchcoding.jsonassert 40 | jsonassert 41 | ${jsonassert.version} 42 | test 43 | 44 | ``` 45 | 46 | .build.gradle (Gradle) 47 | ```groovy 48 | testImplementation "com.toomuchcoding.jsonassert:jsonassert:${jsonassertVersion}" 49 | ``` 50 | 51 | === Assertions 52 | 53 | The library has a couple of main classes. One is `JsonAssertion` that gives you public static methods: 54 | 55 | [source,java] 56 | ---- 57 | public static JsonVerifiable assertThat(String body) 58 | ---- 59 | 60 | NOTE! The aforementioned version caches the `DocumentContext` for the provided JSON. 61 | 62 | and 63 | 64 | [source,java] 65 | ---- 66 | public static JsonVerifiable assertThat(DocumentContext parsedJson) 67 | ---- 68 | 69 | Both these methods give return the public `JsonVerifiable` interface which is a fluent interface with which you can build your 70 | JSON path expression. Please check the Javadocs of that file for more information. 71 | 72 | === Building JSON Paths 73 | 74 | As you could see it's not that easy to build a JSON Path. With JSON Assert you can use the `JsonPath` class to finally 75 | build the JSON Paths yourself! This is the contract for the `JsonPath` class: 76 | 77 | [source,java] 78 | ---- 79 | /** 80 | * Returns a builder of {@link JsonVerifiable} with which you can build your 81 | * JSON Path. Once finished just call {@link JsonVerifiable#jsonPath()} to get 82 | * JSON Path as String. 83 | */ 84 | public static JsonVerifiable builder() 85 | ---- 86 | 87 | and when you call: 88 | 89 | [source,java] 90 | ---- 91 | JsonPath.builder().field("some").field("nested").array("withlist").contains("name").isEqualTo("name1").jsonPath(); 92 | ---- 93 | 94 | you will receive `$.some.nested.withlist[*][?(@.name == 'name1')]` String. 95 | 96 | === Retrieving JSON Path value (since 0.4.0) 97 | 98 | Wouldn't it be great to retrieve the value from the JSON via the JSON Path? There you go! 99 | 100 | [source,groovy] 101 | ---- 102 | given: 103 | String json = ''' [ { 104 | "some" : { 105 | "nested" : { 106 | "json" : "with value", 107 | "anothervalue": 4, 108 | "withlist" : [ 109 | { "name" :"name1"} , 110 | {"name": "name2"}, 111 | {"anothernested": { "name": "name3"} } 112 | ] 113 | } 114 | } 115 | }, 116 | { 117 | "someother" : { 118 | "nested" : { 119 | "json" : true, 120 | "anothervalue": 4, 121 | "withlist" : [ 122 | { "name" :"name1"} , {"name": "name2"} 123 | ], 124 | "withlist2" : [ 125 | "a", "b" 126 | ] 127 | } 128 | } 129 | } 130 | ] 131 | ''' 132 | expect: 133 | com.toomuchcoding.jsonassert.JsonPath.builder(json).array().field("some").field("nested").field("json").read(String) == 'with value' 134 | com.toomuchcoding.jsonassert.JsonPath.builder(json).array().field("some").field("nested").field("anothervalue").read(Integer) == 4 135 | assertThat(json).array().field("some").field("nested").array("withlist").field("name").read(List) == ['name1', 'name2'] 136 | assertThat(json).array().field("someother").field("nested").array("withlist2").read(List) == ['a', 'b'] 137 | assertThat(json).array().field("someother").field("nested").field("json").read(Boolean) == true 138 | ---- 139 | 140 | All thanks to the `JsonReader` interface: 141 | 142 | [source,java] 143 | ---- 144 | /** 145 | * Returns the value from the JSON, based on the created JSON Path. If the result is an 146 | * JSON Array and has a single value then that value is returned. If that's an array with 147 | * greater number of results then that array is returned. 148 | */ 149 | T read(Class clazz); 150 | ---- 151 | 152 | == How to add it 153 | 154 | Just add it as your dependency (Example for Gradle) 155 | 156 | [source,groovy,subs="attributes,verbatim"] 157 | ---- 158 | testCompile 'com.toomuchcoding.jsonassert:jsonassert:{lib_version}' 159 | ---- 160 | 161 | == Dependencies 162 | 163 | JSON Assert is really lightweight. It has only one dependency 164 | 165 | [source,groovy] 166 | ---- 167 | compile "com.jayway.jsonpath:json-path:2.2.0" 168 | ---- 169 | 170 | == Examples 171 | 172 | === Example 1 173 | 174 | For the JSON 175 | 176 | [source,json] 177 | ---- 178 | { 179 | "some" : { 180 | "nested" : { 181 | "json" : "with \"val'ue", 182 | "anothervalue": 4, 183 | "withlist" : [ 184 | { "name" :"name1"} , 185 | {"name": "name2"} 186 | ] 187 | } 188 | } 189 | } 190 | ---- 191 | 192 | The following is true 193 | 194 | JSON Assert expressions: 195 | 196 | [source,java] 197 | ---- 198 | JsonAssertion.assertThat(json).field("some").field("nested").field("anothervalue").isEqualTo(4) 199 | JsonAssertion.assertThat(json).field("some").field("nested").array("withlist").contains("name").isEqualTo("name1") 200 | JsonAssertion.assertThat(json).field("some").field("nested").array("withlist").contains("name").isEqualTo("name2") 201 | JsonAssertion.assertThat(json).field("some").field("nested").field("json").isEqualTo("with \"val'ue") 202 | ---- 203 | 204 | Respective JSON Path expressions: 205 | 206 | [source] 207 | ---- 208 | $.some.nested[?(@.anothervalue == 4)] 209 | $.some.nested.withlist[*][?(@.name == 'name1')] 210 | $.some.nested.withlist[*][?(@.name == 'name2')] 211 | $.some.nested[?(@.json == 'with "val\\'ue')] 212 | ---- 213 | 214 | === Example 2 215 | 216 | For the JSON 217 | 218 | [source,json] 219 | ---- 220 | [{ 221 | "place": 222 | { 223 | "bounding_box": 224 | { 225 | "coordinates": 226 | [[ 227 | [-77.119759,38.995548], 228 | [-76.909393,38.791645] 229 | ]] 230 | } 231 | } 232 | }] 233 | ---- 234 | 235 | The following is true 236 | 237 | JSON Assert expressions: 238 | 239 | [source,java] 240 | ---- 241 | JsonAssertion.assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().arrayField().contains(38.995548).value() 242 | JsonAssertion.assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().arrayField().contains(-77.119759).value() 243 | JsonAssertion.assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().arrayField().contains(-76.909393).value() 244 | JsonAssertion.assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().arrayField().contains(38.791645).value() 245 | ---- 246 | 247 | Respective JSON Path expressions: 248 | 249 | [source] 250 | ---- 251 | $[*].place.bounding_box.coordinates[*][*][?(@ == 38.995548)] 252 | $[*].place.bounding_box.coordinates[*][*][?(@ == -77.119759)] 253 | $[*].place.bounding_box.coordinates[*][*][?(@ == -76.909393)] 254 | $[*].place.bounding_box.coordinates[*][*][?(@ == 38.791645)] 255 | ---- 256 | 257 | === More examples 258 | 259 | More examples can be found in the `JsonAssertionSpec` in the test sources 260 | 261 | = Additional features 262 | 263 | == Shaded library (since 0.5.0) 264 | 265 | Since Jayway's library is quite old, it includes old versions of libraries such as `asm`. We've noticed issues 266 | in other projects that include newer versions of those libraries. The user has 2 options, either exclude the 267 | transitive dependencies and hope that the newer ones will still work or use the new `jsonassert-shade` library 268 | that comes with shaded versions of all the dependencies. 269 | 270 | == AssertJ integration (since 0.2.0) 271 | 272 | There is a possibility to use JSON Assert via AssertJ. Regardless of which version you'll choose 273 | you have the same class that you can use to start the fluent assertion 274 | 275 | The standard version 276 | 277 | [source,java] 278 | ---- 279 | com.toomuchcoding.jsonassert.JsonAssertions.assertThat(DocumentContext context); 280 | com.toomuchcoding.jsonassert.JsonAssertions.assertThat(JsonVerifiable jsonVerifiable); 281 | ---- 282 | 283 | or the BDD version 284 | 285 | [source,java] 286 | ---- 287 | com.toomuchcoding.jsonassert.BDDJsonAssertions.then(DocumentContext context); 288 | com.toomuchcoding.jsonassert.BDDJsonAssertions.then(JsonVerifiable jsonVerifiable); 289 | ---- 290 | 291 | === AssertJ 2.x 292 | 293 | Just add 294 | 295 | [source,groovy,subs="attributes,verbatim"] 296 | ---- 297 | testCompile 'com.toomuchcoding.jsonassert:jsonassert-assertj-java7:{lib_version}' 298 | ---- 299 | 300 | === AssertJ 3.x 301 | 302 | Just add 303 | 304 | [source,groovy,subs="attributes,verbatim"] 305 | -- 306 | testCompile 'com.toomuchcoding.jsonassert:jsonassert-assertj-java8:{lib_version}' 307 | -- 308 | 309 | = Migrations 310 | 311 | == From 0.1.x -> 0.2.0 312 | 313 | - Convert all project imports from `com.blogspot.toomuchcoding:jsonassert` to `com.toomuchcoding.jsonassert:jsonassert` 314 | - Convert all code imports from `com.blogspot.toomuchcoding.jsonassert` to `com.toomuchcoding.jsonassert` 315 | 316 | = Contact 317 | 318 | https://gitter.im/marcingrzejszczak/jsonassert[Gitter chat] 319 | -------------------------------------------------------------------------------- /jsonassert-assertj-java8/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | jsonassert-assertj-java8 5 | 0.8.1-SNAPSHOT 6 | 7 | 8 | com.toomuchcoding.jsonassert 9 | jsonassert-parent 10 | 0.8.1-SNAPSHOT 11 | .. 12 | 13 | 14 | 15 | 3.26.0 16 | 17 | 18 | 19 | com.jayway.jsonpath 20 | json-path 21 | ${json-path.version} 22 | compile 23 | 24 | 25 | org.slf4j 26 | slf4j-api 27 | 28 | 29 | 30 | 31 | org.slf4j 32 | slf4j-api 33 | ${slf4j-api.version} 34 | runtime 35 | 36 | 37 | com.toomuchcoding.jsonassert 38 | jsonassert 39 | ${project.version} 40 | compile 41 | 42 | 43 | org.assertj 44 | assertj-core 45 | ${assertj.version} 46 | compile 47 | 48 | 49 | org.apache.groovy 50 | groovy-json 51 | ${groovy.version} 52 | test 53 | 54 | 55 | org.spockframework 56 | spock-core 57 | ${spock.version} 58 | test 59 | 60 | 61 | cglib 62 | cglib-nodep 63 | ${cglib-nodep.version} 64 | test 65 | 66 | 67 | org.objenesis 68 | objenesis 69 | ${objenesis.version} 70 | test 71 | 72 | 73 | ch.qos.logback 74 | logback-classic 75 | ${logback-classic.version} 76 | test 77 | 78 | 79 | 80 | 81 | 82 | 83 | maven-javadoc-plugin 84 | 85 | 86 | maven-source-plugin 87 | 88 | 89 | org.codehaus.gmavenplus 90 | gmavenplus-plugin 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /jsonassert-assertj-java8/src/main/java/com/toomuchcoding/jsonassert/BDDJsonAssertions.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | import org.assertj.core.api.BDDAssertions; 5 | 6 | /** 7 | * Entry point for {@link DocumentContext} {@link BDDAssertions} 8 | * 9 | * @author Marcin Grzejszczak 10 | * 11 | * @since 0.2.0 12 | */ 13 | public class BDDJsonAssertions extends BDDAssertions { 14 | 15 | public static JsonPathAssert then(DocumentContext actual) { 16 | return new JsonPathAssert(actual); 17 | } 18 | 19 | public static JsonPathAssert then(JsonVerifiable actual) { 20 | return new JsonPathAssert(actual); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /jsonassert-assertj-java8/src/main/java/com/toomuchcoding/jsonassert/JsonAssertions.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | import org.assertj.core.api.Assertions; 5 | 6 | /** 7 | * Entry point for {@link DocumentContext} {@link Assertions} 8 | * 9 | * @author Marcin Grzejszczak 10 | * 11 | * @since 0.2.0 12 | */ 13 | public class JsonAssertions extends Assertions { 14 | 15 | public static JsonPathAssert assertThat(DocumentContext actual) { 16 | return new JsonPathAssert(actual); 17 | } 18 | 19 | public static JsonPathAssert assertThat(JsonVerifiable actual) { 20 | return new JsonPathAssert(actual); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /jsonassert-assertj-java8/src/main/java/com/toomuchcoding/jsonassert/JsonPathAssert.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import org.assertj.core.api.AbstractAssert; 4 | 5 | import com.jayway.jsonpath.DocumentContext; 6 | 7 | /** 8 | * A AssertJ version of JSON Assert. 9 | * 10 | * The methods used by JSON Assert are available as assertions of either 11 | * {@link DocumentContext} or {@link JsonVerifiable}. 12 | * 13 | * Remember that the order of execution matters since it's building the JSON Path 14 | * in the provided sequence. 15 | * 16 | * @author Marcin Grzejszczak 17 | * 18 | * @since 0.2.0 19 | */ 20 | public class JsonPathAssert extends AbstractAssert { 21 | 22 | public JsonPathAssert(DocumentContext actual) { 23 | super(JsonAssertion.assertThatJson(actual), JsonPathAssert.class); 24 | } 25 | 26 | public JsonPathAssert(JsonVerifiable actual) { 27 | super(actual, JsonPathAssert.class); 28 | } 29 | 30 | /** 31 | * @see JsonVerifiable#contains(Object) 32 | */ 33 | public JsonPathAssert contains(Object value) { 34 | isNotNull(); 35 | return new JsonPathAssert(actual.contains(value)); 36 | } 37 | 38 | /** 39 | * @see JsonVerifiable#field(Object) 40 | */ 41 | public JsonPathAssert field(Object value) { 42 | isNotNull(); 43 | return new JsonPathAssert(actual.field(value)); 44 | } 45 | 46 | /** 47 | * @see JsonVerifiable#field(String...) 48 | */ 49 | public JsonPathAssert field(String... value) { 50 | isNotNull(); 51 | return new JsonPathAssert(actual.field(value)); 52 | } 53 | 54 | /** 55 | * @see JsonVerifiable#array()} (Object) 56 | */ 57 | public JsonPathAssert array(Object value) { 58 | isNotNull(); 59 | return new JsonPathAssert(actual.array(value)); 60 | } 61 | 62 | /** 63 | * @see JsonVerifiable#arrayField() 64 | */ 65 | public JsonPathAssert arrayField() { 66 | isNotNull(); 67 | return new JsonPathAssert(actual.arrayField()); 68 | } 69 | 70 | /** 71 | * @see JsonVerifiable#array() 72 | */ 73 | public JsonPathAssert array() { 74 | isNotNull(); 75 | return new JsonPathAssert(actual.array()); 76 | } 77 | 78 | /** 79 | * @see JsonVerifiable#isEqualTo(String) 80 | */ 81 | public JsonPathAssert isEqualTo(String value) { 82 | isNotNull(); 83 | JsonVerifiable jsonVerifiable = null; 84 | try { 85 | jsonVerifiable = actual.isEqualTo(value); 86 | } catch (IllegalStateException e) { 87 | failWithMessage("Expected JSON to match JSON Path <%s> but it didn't", actual.jsonPath()); 88 | } 89 | return new JsonPathAssert(jsonVerifiable); 90 | } 91 | 92 | /** 93 | * @see JsonVerifiable#isEqualTo(Number) 94 | */ 95 | public JsonPathAssert isEqualTo(Number value) { 96 | isNotNull(); 97 | JsonVerifiable jsonVerifiable = null; 98 | try { 99 | jsonVerifiable = actual.isEqualTo(value); 100 | } catch (IllegalStateException e) { 101 | failWithMessage("Expected JSON to match JSON Path <%s> but it didn't", actual.jsonPath()); 102 | } 103 | return new JsonPathAssert(jsonVerifiable); 104 | } 105 | 106 | /** 107 | * @see JsonVerifiable#isInstanceOf(Class) 108 | */ 109 | public JsonPathAssert isInstanceOf(Class clazz) { 110 | isNotNull(); 111 | JsonVerifiable jsonVerifiable = null; 112 | try { 113 | jsonVerifiable = actual.isInstanceOf(clazz); 114 | } catch (IllegalStateException e) { 115 | failWithMessage(e.getMessage()); 116 | } 117 | return new JsonPathAssert(jsonVerifiable); 118 | } 119 | 120 | /** 121 | * @see JsonVerifiable#matches(String) 122 | */ 123 | public JsonPathAssert matches(String value) { 124 | isNotNull(); 125 | JsonVerifiable jsonVerifiable = null; 126 | try { 127 | jsonVerifiable = actual.matches(value); 128 | } catch (IllegalStateException e) { 129 | failWithMessage("Expected JSON to match JSON Path <%s> but it didn't", actual.jsonPath()); 130 | } 131 | return new JsonPathAssert(jsonVerifiable); 132 | } 133 | 134 | /** 135 | * @see JsonVerifiable#isEqualTo(Boolean) 136 | */ 137 | public JsonPathAssert isEqualTo(Boolean value) { 138 | isNotNull(); 139 | JsonVerifiable jsonVerifiable = null; 140 | try { 141 | jsonVerifiable = actual.isEqualTo(value); 142 | } catch (IllegalStateException e) { 143 | failWithMessage("Expected JSON to match JSON Path <%s> but it didn't", actual.jsonPath()); 144 | } 145 | return new JsonPathAssert(jsonVerifiable); 146 | } 147 | 148 | /** 149 | * @see JsonVerifiable#value() 150 | */ 151 | public JsonPathAssert value() { 152 | isNotNull(); 153 | JsonVerifiable jsonVerifiable = null; 154 | try { 155 | jsonVerifiable = actual.value(); 156 | } catch (IllegalStateException e) { 157 | failWithMessage("Expected JSON to match JSON Path <%s> but it didn't", actual.jsonPath()); 158 | } 159 | return new JsonPathAssert(jsonVerifiable); 160 | } 161 | 162 | /** 163 | * @see JsonVerifiable#isNull() 164 | */ 165 | @Override 166 | public void isNull() { 167 | isNotNull(); 168 | try { 169 | actual.isNull(); 170 | } catch (IllegalStateException e) { 171 | failWithMessage("Expected JSON to match JSON Path <%s> but it didn't", actual.jsonPath()); 172 | } 173 | } 174 | 175 | /** 176 | * @see JsonVerifiable#matchesJsonPath(String) 177 | */ 178 | public JsonPathAssert matchesJsonPath(String jsonPath) { 179 | isNotNull(); 180 | try { 181 | actual.matchesJsonPath(jsonPath); 182 | } catch (IllegalStateException e) { 183 | failWithMessage("Expected JSON to match JSON Path <%s> but it didn't", jsonPath); 184 | } 185 | return this; 186 | } 187 | 188 | /** 189 | * @see JsonVerifiable#isEmpty() 190 | */ 191 | public JsonPathAssert isEmpty() { 192 | isNotNull(); 193 | try { 194 | actual.isEmpty(); 195 | } catch (IllegalStateException e) { 196 | failWithMessage("Expected JSON to with JSON Path <%s> to be empty", actual.jsonPath()); 197 | } 198 | return this; 199 | } 200 | 201 | } -------------------------------------------------------------------------------- /jsonassert-assertj-java8/src/test/groovy/com/toomuchcoding/jsonassert/JsonAssertionWithAssertJJava8Spec.groovy: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert 2 | 3 | import com.jayway.jsonpath.JsonPath 4 | import spock.lang.Issue 5 | import spock.lang.Shared 6 | import spock.lang.Specification 7 | import spock.lang.Unroll 8 | 9 | import static com.toomuchcoding.jsonassert.JsonAssertions.assertThat 10 | import static groovy.json.JsonOutput.toJson 11 | 12 | /** 13 | * @author Marcin Grzejszczak 14 | */ 15 | class JsonAssertionWithAssertJJava8Spec extends Specification { 16 | 17 | @Shared String json1 = ''' 18 | { 19 | "some" : { 20 | "nested" : { 21 | "json" : "with \\"val'ue", 22 | "anothervalue": 4, 23 | "withlist" : [ 24 | { "name" :"name1"} , {"name": "name2"} 25 | ] 26 | } 27 | } 28 | } 29 | ''' 30 | 31 | @Unroll 32 | def 'should convert a json with a map as root to a map of path to value '() { 33 | expect: 34 | assertThat(JsonPath.parse(json1)).field("some").field("nested").field("anothervalue").isEqualTo(4) 35 | BDDJsonAssertions.then(JsonPath.parse(json1)).field("some").field("nested").field("anothervalue").isEqualTo(4) 36 | assertThat(JsonPath.parse(json1)).field("some").field("nested").array("withlist").contains("name").isEqualTo("name1") 37 | assertThat(JsonPath.parse(json1)).field("some").field("nested").array("withlist").contains("name").isEqualTo("name2") 38 | assertThat(JsonPath.parse(json1)).field("some").field("nested").field("json").isEqualTo("with \"val'ue") 39 | assertThat(JsonPath.parse(json1)).field("some", "nested", "json").isEqualTo("with \"val'ue") 40 | } 41 | 42 | @Shared String json2 = '''{ 43 | "property1": "a", 44 | "property2": "b" 45 | }''' 46 | 47 | @Unroll 48 | def "should generate assertions for simple response body"() { 49 | expect: 50 | assertThat(JsonPath.parse(json2)).field("property1").isEqualTo("a") 51 | assertThat(JsonPath.parse(json2)).field("property2").isEqualTo("b") 52 | } 53 | 54 | @Shared String json3 = '''{ 55 | "property1": "true", 56 | "property2": null, 57 | "property3": false 58 | }''' 59 | 60 | @Unroll 61 | def "should generate assertions for null and boolean values"() { 62 | expect: 63 | assertThat(JsonPath.parse(json3)).field("property1").isEqualTo("true") 64 | assertThat(JsonPath.parse(json3)).field("property2").isNull() 65 | assertThat(JsonPath.parse(json3)).field("property3").isEqualTo(false) 66 | } 67 | 68 | @Shared String json6 = '''[ 69 | { 70 | "property1": "a" 71 | }, 72 | { 73 | "property2": "b" 74 | }]''' 75 | 76 | @Unroll 77 | def "should generate assertions for array in response body"() { 78 | expect: 79 | assertThat(JsonPath.parse(json6)).array().contains("property1").isEqualTo("a") 80 | assertThat(JsonPath.parse(json6)).array().contains("property2").isEqualTo("b") 81 | } 82 | 83 | @Shared String json7 = '''{ 84 | "property1": [ 85 | { "property2": "test1"}, 86 | { "property3": "test2"} 87 | ] 88 | }''' 89 | 90 | @Unroll 91 | def "should generate assertions for array inside response body element"() { 92 | expect: 93 | assertThat(JsonPath.parse(json7)).array("property1").contains("property2").isEqualTo("test1") 94 | assertThat(JsonPath.parse(json7)).array("property1").contains("property3").isEqualTo("test2") 95 | } 96 | 97 | @Shared String json8 = """{ 98 | "property1": "a", 99 | "property2": {"property3": "b"} 100 | }""" 101 | 102 | def "should generate assertions for nested objects in response body"() { 103 | expect: 104 | assertThat(JsonPath.parse(json8)).field("property2").field("property3").isEqualTo("b") 105 | assertThat(JsonPath.parse(json8)).field("property1").isEqualTo("a") 106 | } 107 | 108 | @Shared String json11 = ''' 109 | [{ 110 | "place": 111 | { 112 | "bounding_box": 113 | { 114 | "coordinates": 115 | [[ 116 | [-77.119759,38.995548], 117 | [-76.909393,38.791645] 118 | ]] 119 | } 120 | } 121 | }] 122 | ''' 123 | 124 | @Unroll 125 | def "should manage to parse a triple array"() { 126 | expect: 127 | assertThat(JsonPath.parse(json11)).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(38.995548).value() 128 | assertThat(JsonPath.parse(json11)).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(-77.119759).value() 129 | assertThat(JsonPath.parse(json11)).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(-76.909393).value() 130 | assertThat(JsonPath.parse(json11)).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(38.791645).value() 131 | 132 | } 133 | 134 | @Unroll 135 | def 'should convert a json with list as root to a map of path to value'() { 136 | expect: 137 | assertThat(JsonPath.parse(json)).array().field("some").field("nested").field("json").isEqualTo("with value") 138 | assertThat(JsonPath.parse(json)).array().field("some").field("nested").field("anothervalue").isEqualTo(4) 139 | assertThat(JsonPath.parse(json)).array().field("some").field("nested").array("withlist").contains("name").isEqualTo("name1") 140 | assertThat(JsonPath.parse(json)).array().field("some").field("nested").array("withlist").contains("name").isEqualTo("name2") 141 | assertThat(JsonPath.parse(json)).array().field("some").field("nested").array("withlist").field("anothernested").field("name").isEqualTo("name3") 142 | where: 143 | json << [ 144 | ''' 145 | [ { 146 | "some" : { 147 | "nested" : { 148 | "json" : "with value", 149 | "anothervalue": 4, 150 | "withlist" : [ 151 | { "name" :"name1"} , {"name": "name2"}, {"anothernested": { "name": "name3"} } 152 | ] 153 | } 154 | } 155 | }, 156 | { 157 | "someother" : { 158 | "nested" : { 159 | "json" : "with value", 160 | "anothervalue": 4, 161 | "withlist" : [ 162 | { "name" :"name1"} , {"name": "name2"} 163 | ] 164 | } 165 | } 166 | } 167 | ] 168 | ''', 169 | ''' 170 | [{ 171 | "someother" : { 172 | "nested" : { 173 | "json" : "with value", 174 | "anothervalue": 4, 175 | "withlist" : [ 176 | { "name" :"name1"} , {"name": "name2"} 177 | ] 178 | } 179 | } 180 | }, 181 | { 182 | "some" : { 183 | "nested" : { 184 | "json" : "with value", 185 | "anothervalue": 4, 186 | "withlist" : [ 187 | {"name": "name2"}, {"anothernested": { "name": "name3"} }, { "name" :"name1"} 188 | ] 189 | } 190 | } 191 | } 192 | ]'''] 193 | } 194 | 195 | def "should run json path when provided manually"() { 196 | given: 197 | String json = """{ 198 | "property1": "a", 199 | "property2": {"property3": "b"} 200 | }""" 201 | and: 202 | String jsonPath = '''$[?(@.property1 == 'a')]''' 203 | expect: 204 | assertThat(JsonPath.parse(json)).matchesJsonPath(jsonPath) 205 | } 206 | 207 | def "should throw exception when json path is not matched against provided json path"() { 208 | given: 209 | String json = """{ 210 | "property1": "a", 211 | "property2": {"property3": "b"} 212 | }""" 213 | and: 214 | String jsonPath = '''$[?(@.property1 == 'c')]''' 215 | when: 216 | assertThat(JsonPath.parse(json)).matchesJsonPath(jsonPath) 217 | then: 218 | AssertionError assertionError = thrown(AssertionError) 219 | assertionError.message.contains("Expected JSON to match JSON Path") 220 | } 221 | 222 | def "should throw exception when json path is not matched"() { 223 | given: 224 | String json = """{ 225 | "property1": "a", 226 | "property2": {"property3": "b"} 227 | }""" 228 | when: 229 | assertThat(JsonPath.parse(json)).field("property2").field("property3").isEqualTo("c") 230 | then: 231 | AssertionError assertionError = thrown(AssertionError) 232 | assertionError.message == '''Expected JSON to match JSON Path <$.property2[?(@.property3 == 'c')]> but it didn't''' 233 | } 234 | 235 | 236 | def "should generate escaped regex assertions for boolean objects in response body"() { 237 | given: 238 | Map json = [ 239 | property2: true 240 | ] 241 | expect: 242 | assertThat(JsonPath.parse(toJson(json))).field("property2").matches('true|false') 243 | } 244 | 245 | def "should generate escaped regex assertions for numbers objects in response body"() { 246 | given: 247 | Map json = [ 248 | property2: 50 249 | ] 250 | expect: 251 | assertThat(JsonPath.parse(toJson(json))).field("property2").matches('[0-9]{2}') 252 | } 253 | 254 | @Issue('#14') 255 | def 'should allow to check if size is empty'() { 256 | given: 257 | String json = '''{ "coordinates" : [], "foo": ["bar", "baz"] }''' 258 | expect: 259 | assertThat(JsonPath.parse(json)).array("coordinates").isEmpty() 260 | when: 261 | assertThat(JsonPath.parse(json)).array("foo").isEmpty() 262 | then: 263 | AssertionError assertionError = thrown(AssertionError) 264 | assertionError.message == '''Expected JSON to with JSON Path <$.foo[*]> to be empty''' 265 | } 266 | 267 | @Issue('#18') 268 | def 'should read types of objects'() { 269 | given: 270 | String json = '''{ "foo" : 46 }''' 271 | when: 272 | assertThat(JsonPath.parse(json)).field("foo").isInstanceOf(Number) 273 | then: 274 | noExceptionThrown() 275 | when: 276 | assertThat(JsonPath.parse(json)).field("foo").isInstanceOf(String) 277 | then: 278 | AssertionError assertionError = thrown(AssertionError) 279 | assertionError.message == '''For JSON path [$.foo] instance of [Integer] is not assignable from [String]''' 280 | } 281 | 282 | 283 | } -------------------------------------------------------------------------------- /jsonassert-shade/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | jsonassert-shade 5 | 0.8.1-SNAPSHOT 6 | 7 | 8 | com.toomuchcoding.jsonassert 9 | jsonassert-parent 10 | 0.8.1-SNAPSHOT 11 | .. 12 | 13 | 14 | 15 | 3.6.0 16 | 3.6.1 17 | 3.6.0 18 | 19 | 20 | 2.17.1 21 | 2.11.0 22 | 5.8.6 23 | 1.5.4 24 | 20231013 25 | 26 | 27 | 28 | 29 | ${project.groupId} 30 | jsonassert 31 | ${project.version} 32 | compile 33 | 34 | 35 | com.fasterxml.jackson.core 36 | jackson-databind 37 | ${jackson-databind.version} 38 | provided 39 | true 40 | 41 | 42 | com.google.code.gson 43 | gson 44 | ${gson.version} 45 | provided 46 | true 47 | 48 | 49 | org.json 50 | json 51 | ${json.version} 52 | provided 53 | true 54 | 55 | 56 | org.apache.tapestry 57 | tapestry-json 58 | ${tapestry-json.version} 59 | provided 60 | true 61 | 62 | 63 | org.codehaus.jettison 64 | jettison 65 | ${jettison.version} 66 | provided 67 | true 68 | 69 | 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-shade-plugin 76 | ${maven-shade-plugin.version} 77 | 78 | 79 | package 80 | 81 | shade 82 | 83 | 84 | true 85 | false 86 | true 87 | true 88 | false 89 | 90 | 91 | 92 | com.jayway 93 | shaded.com.jayway 94 | 95 | 96 | 97 | net.minidev 98 | shaded.net.minidev 99 | 100 | 101 | 102 | org.objectweb 103 | shaded.org.objectweb 104 | 105 | 106 | 107 | 108 | 109 | 110 | com/toomuchcoding/ 111 | org/slf4j/ 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-dependency-plugin 122 | ${maven-dependency-plugin.version} 123 | 124 | 125 | unpack-dependencies 126 | package 127 | 128 | unpack 129 | 130 | 131 | 132 | 133 | ${project.groupId} 134 | ${project.artifactId} 135 | ${project.version} 136 | sources 137 | jar 138 | false 139 | ${project.build.directory}/sources 140 | 141 | 142 | 143 | **/shaded/**/*.*,**/toomuchcoding/**/*.* 144 | 145 | 146 | 147 | 148 | 149 | 150 | org.codehaus.mojo 151 | build-helper-maven-plugin 152 | ${build-helper-maven-plugin.version} 153 | 154 | 155 | add-source 156 | package 157 | 158 | add-source 159 | 160 | 161 | 162 | ${project.build.directory}/sources 163 | 164 | 165 | 166 | 167 | 168 | 169 | org.apache.maven.plugins 170 | maven-surefire-plugin 171 | 172 | false 173 | 174 | 175 | 176 | maven-javadoc-plugin 177 | 178 | false 179 | false 180 | 181 | 182 | 183 | maven-source-plugin 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /jsonassert/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | jsonassert 5 | 0.8.1-SNAPSHOT 6 | 7 | 8 | com.toomuchcoding.jsonassert 9 | jsonassert-parent 10 | 0.8.1-SNAPSHOT 11 | .. 12 | 13 | 14 | 15 | 16 | com.jayway.jsonpath 17 | json-path 18 | compile 19 | 20 | 21 | net.minidev 22 | json-smart 23 | ${json-smart.version} 24 | compile 25 | 26 | 27 | org.slf4j 28 | slf4j-api 29 | ${slf4j-api.version} 30 | compile 31 | 32 | 33 | org.apache.groovy 34 | groovy-json 35 | ${groovy.version} 36 | test 37 | 38 | 39 | org.spockframework 40 | spock-core 41 | ${spock.version} 42 | test 43 | 44 | 45 | cglib 46 | cglib-nodep 47 | ${cglib-nodep.version} 48 | test 49 | 50 | 51 | org.objenesis 52 | objenesis 53 | ${objenesis.version} 54 | test 55 | 56 | 57 | ch.qos.logback 58 | logback-classic 59 | ${logback-classic.version} 60 | test 61 | 62 | 63 | 64 | 65 | 66 | 67 | maven-javadoc-plugin 68 | 69 | 70 | maven-source-plugin 71 | 72 | 73 | org.codehaus.gmavenplus 74 | gmavenplus-plugin 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/ArrayAssertion.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | 5 | import java.util.LinkedList; 6 | 7 | class ArrayAssertion extends JsonAsserter { 8 | protected ArrayAssertion(DocumentContext parsedJson, LinkedList jsonPathBuffer, 9 | Object arrayName, JsonAsserterConfiguration jsonAsserterConfiguration) { 10 | super(parsedJson, jsonPathBuffer, arrayName, jsonAsserterConfiguration); 11 | } 12 | 13 | protected ArrayAssertion(DocumentContext parsedJson, LinkedList jsonPathBuffer, 14 | JsonAsserterConfiguration jsonAsserterConfiguration) { 15 | super(parsedJson, jsonPathBuffer, null, jsonAsserterConfiguration); 16 | } 17 | 18 | @Override 19 | public boolean isIteratingOverArray() { 20 | return true; 21 | } 22 | } -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/ArrayValueAssertion.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | 5 | import java.util.LinkedList; 6 | 7 | class ArrayValueAssertion extends FieldAssertion { 8 | protected ArrayValueAssertion(DocumentContext parsedJson, LinkedList jsonPathBuffer, 9 | Object arrayName, JsonAsserterConfiguration jsonAsserterConfiguration) { 10 | super(parsedJson, jsonPathBuffer, arrayName, jsonAsserterConfiguration); 11 | } 12 | 13 | protected ArrayValueAssertion(DocumentContext parsedJson, LinkedList jsonPathBuffer, 14 | JsonAsserterConfiguration jsonAsserterConfiguration) { 15 | super(parsedJson, jsonPathBuffer, null, jsonAsserterConfiguration); 16 | } 17 | 18 | @Override 19 | public JsonVerifiable contains(Object value) { 20 | return new ArrayValueAssertion(parsedJson, jsonPathBuffer, value, jsonAsserterConfiguration).isEqualTo(value); 21 | } 22 | 23 | @Override 24 | public JsonVerifiable isEqualTo(String value) { 25 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 26 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 27 | readyToCheck.jsonPathBuffer.offer("[?(@ == " + wrapValueWithSingleQuotes(value) + ")]"); 28 | readyToCheck.checkBufferedJsonPathString(); 29 | return readyToCheck; 30 | } 31 | 32 | @Override 33 | public JsonVerifiable isEqualTo(Number value) { 34 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 35 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 36 | readyToCheck.jsonPathBuffer.offer("[?(@ == " + numberValue(value) + ")]"); 37 | readyToCheck.checkBufferedJsonPathString(); 38 | return readyToCheck; 39 | } 40 | 41 | @Override 42 | public JsonVerifiable matches(String value) { 43 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 44 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 45 | readyToCheck.jsonPathBuffer.offer("[?(@ =~ /" + stringWithEscapedSingleQuotesForRegex(value) + "/)]"); 46 | readyToCheck.checkBufferedJsonPathString(); 47 | return readyToCheck; 48 | } 49 | 50 | @Override 51 | public JsonVerifiable isEqualTo(Boolean value) { 52 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 53 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 54 | readyToCheck.jsonPathBuffer.offer("[?(@ == " + String.valueOf(value) + ")]"); 55 | readyToCheck.checkBufferedJsonPathString(); 56 | return readyToCheck; 57 | } 58 | 59 | @Override 60 | public boolean isAssertingAValueInArray() { 61 | return true; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/FieldAssertion.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | 5 | import java.util.LinkedList; 6 | 7 | class FieldAssertion extends JsonAsserter { 8 | protected FieldAssertion(DocumentContext parsedJson, LinkedList jsonPathBuffer, 9 | Object value, JsonAsserterConfiguration jsonAsserterConfiguration) { 10 | super(parsedJson, jsonPathBuffer, value, jsonAsserterConfiguration); 11 | } 12 | } -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/IteratingOverArray.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | /** 4 | * Helper interface describing the process of current iteration 5 | * 6 | * @author Marcin Grzejszczak 7 | * 8 | * @since 0.1.0 9 | */ 10 | public interface IteratingOverArray { 11 | /** 12 | * True if is in progress of iterating over nameless array 13 | */ 14 | boolean isIteratingOverNamelessArray(); 15 | 16 | /** 17 | * True if is in progress of iterating over an array 18 | */ 19 | boolean isIteratingOverArray(); 20 | 21 | /** 22 | * True if current element is a particular value on which concrete assertion will take place 23 | */ 24 | boolean isAssertingAValueInArray(); 25 | 26 | } -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/JsonAsserter.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | import net.minidev.json.JSONArray; 5 | 6 | import java.math.BigDecimal; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.regex.Pattern; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | class JsonAsserter implements JsonVerifiable { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(JsonAsserter.class); 18 | 19 | protected final DocumentContext parsedJson; 20 | protected final LinkedList jsonPathBuffer; 21 | protected final Object fieldName; 22 | protected final JsonAsserterConfiguration jsonAsserterConfiguration; 23 | 24 | protected JsonAsserter(DocumentContext parsedJson, LinkedList jsonPathBuffer, 25 | Object fieldName, JsonAsserterConfiguration jsonAsserterConfiguration) { 26 | this.parsedJson = parsedJson; 27 | this.jsonPathBuffer = new LinkedList(jsonPathBuffer); 28 | this.fieldName = fieldName; 29 | this.jsonAsserterConfiguration = jsonAsserterConfiguration; 30 | } 31 | 32 | @Override 33 | public JsonVerifiable contains(final Object value) { 34 | FieldAssertion asserter = new FieldAssertion(parsedJson, jsonPathBuffer, value, jsonAsserterConfiguration); 35 | // this is fake part of jsonpath since in the next section we will remove this entry 36 | asserter.jsonPathBuffer.offer("[*]"); 37 | return asserter; 38 | } 39 | 40 | @Override 41 | public FieldAssertion field(final Object value) { 42 | FieldAssertion asserter = new FieldAssertion(parsedJson, jsonPathBuffer, value, jsonAsserterConfiguration); 43 | asserter.jsonPathBuffer.offer("." + String.valueOf(value)); 44 | return asserter; 45 | } 46 | 47 | @Override 48 | public FieldAssertion field(String... fields) { 49 | FieldAssertion assertion = null; 50 | for(String field : fields) { 51 | assertion = assertion == null ? field(field) : assertion.field(field); 52 | } 53 | return assertion; 54 | } 55 | 56 | @Override 57 | public ArrayAssertion array(final Object value) { 58 | if (value == null) { 59 | return array(); 60 | } 61 | ArrayAssertion asserter = new ArrayAssertion(parsedJson, jsonPathBuffer, value, jsonAsserterConfiguration); 62 | asserter.jsonPathBuffer.offer("." + String.valueOf(value) + "[*]"); 63 | return asserter; 64 | } 65 | 66 | @Override 67 | public ArrayValueAssertion arrayField() { 68 | String peekedLast = jsonPathBuffer.peekLast(); 69 | // Issue #9 70 | if(!"[*]".equals(peekedLast) && peekedLast.endsWith("[*]") || jsonPath().endsWith("[*][*]")) { 71 | String last = jsonPathBuffer.pollLast(); 72 | jsonPathBuffer.offer(last.replace("[*]", "")); 73 | } 74 | return new ArrayValueAssertion(parsedJson, jsonPathBuffer, jsonAsserterConfiguration); 75 | } 76 | 77 | @Override 78 | public ArrayAssertion array() { 79 | ArrayAssertion asserter = new ArrayAssertion(parsedJson, jsonPathBuffer, jsonAsserterConfiguration); 80 | asserter.jsonPathBuffer.offer("[*]"); 81 | return asserter; 82 | } 83 | 84 | @Override 85 | public JsonVerifiable elementWithIndex(int index) { 86 | ArrayAssertion asserter = new ArrayAssertion(parsedJson, jsonPathBuffer, jsonAsserterConfiguration); 87 | asserter.jsonPathBuffer.offer("[" + index + "]"); 88 | return asserter; 89 | } 90 | 91 | @Override 92 | public JsonVerifiable isEqualTo(String value) { 93 | if (value == null) { 94 | return isNull(); 95 | } 96 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 97 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 98 | readyToCheck.jsonPathBuffer.removeLast(); 99 | readyToCheck.jsonPathBuffer.offer("[?(@." + String.valueOf(fieldName) + " == " + wrapValueWithSingleQuotes(value) + ")]"); 100 | updateCurrentBuffer(readyToCheck); 101 | readyToCheck.checkBufferedJsonPathString(); 102 | return readyToCheck; 103 | } 104 | 105 | @Override 106 | public JsonVerifiable isInstanceOf(Class clazz) { 107 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 108 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 109 | Object object = readyToCheck.read(Object.class); 110 | if (clazz.isAssignableFrom(object.getClass())) { 111 | return readyToCheck; 112 | } 113 | throw new IllegalStateException("For JSON path [" + readyToCheck.jsonPath() + "] instance of [" + object.getClass().getSimpleName() + "] is not assignable from [" + clazz.getSimpleName() + "]"); 114 | } 115 | 116 | private void updateCurrentBuffer(JsonAsserter readyToCheck) { 117 | jsonPathBuffer.clear(); 118 | jsonPathBuffer.addAll(readyToCheck.jsonPathBuffer); 119 | } 120 | 121 | @Override 122 | public JsonVerifiable isEqualTo(Object value) { 123 | if (value == null) { 124 | return isNull(); 125 | } 126 | if (value instanceof Number) { 127 | return isEqualTo((Number) value); 128 | } else if (value instanceof Boolean) { 129 | return isEqualTo((Boolean) value); 130 | } else if (value instanceof Pattern) { 131 | return matches(((Pattern) value).pattern()); 132 | } 133 | return isEqualTo(value.toString()); 134 | } 135 | 136 | @Override 137 | public JsonVerifiable isEqualTo(Number value) { 138 | if (value == null) { 139 | return isNull(); 140 | } 141 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 142 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 143 | readyToCheck.jsonPathBuffer.removeLast(); 144 | readyToCheck.jsonPathBuffer.offer("[?(@." + String.valueOf(fieldName) + " == " + numberValue(value) + ")]"); 145 | updateCurrentBuffer(readyToCheck); 146 | readyToCheck.checkBufferedJsonPathString(); 147 | return readyToCheck; 148 | } 149 | 150 | String numberValue(Number number) { 151 | return new BigDecimal(number.toString()).toPlainString(); 152 | } 153 | 154 | @Override 155 | public JsonVerifiable isNull() { 156 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 157 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 158 | readyToCheck.jsonPathBuffer.removeLast(); 159 | readyToCheck.jsonPathBuffer.offer("[?(@." + String.valueOf(fieldName) + " == null)]"); 160 | updateCurrentBuffer(readyToCheck); 161 | readyToCheck.checkBufferedJsonPathString(); 162 | return readyToCheck; 163 | } 164 | 165 | @Override 166 | public JsonVerifiable matches(String value) { 167 | if (value == null) { 168 | return isNull(); 169 | } 170 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 171 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 172 | readyToCheck.jsonPathBuffer.removeLast(); 173 | readyToCheck.jsonPathBuffer.offer("[?(@." + String.valueOf(fieldName) 174 | + " =~ /" + stringWithEscapedSingleQuotesForRegex(value) + "/)]"); 175 | updateCurrentBuffer(readyToCheck); 176 | readyToCheck.checkBufferedJsonPathString(); 177 | return readyToCheck; 178 | } 179 | 180 | @Override 181 | public JsonVerifiable isEqualTo(Boolean value) { 182 | if (value == null) { 183 | return isNull(); 184 | } 185 | ReadyToCheckAsserter readyToCheck = new ReadyToCheckAsserter(parsedJson, 186 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 187 | readyToCheck.jsonPathBuffer.removeLast(); 188 | readyToCheck.jsonPathBuffer.offer("[?(@." + String.valueOf(fieldName) + " == " + String.valueOf(value) + ")]"); 189 | updateCurrentBuffer(readyToCheck); 190 | readyToCheck.checkBufferedJsonPathString(); 191 | return readyToCheck; 192 | } 193 | 194 | @Override public JsonVerifiable isEmpty() throws IllegalStateException { 195 | if (jsonAsserterConfiguration.ignoreJsonPathException) { 196 | logOverridingWarning(); 197 | return this; 198 | } 199 | String jsonPathString = createJsonPathString(); 200 | Object o = parsedJson.read(jsonPathString); 201 | if (!isObjectEmpty(o)) { 202 | throw new IllegalStateException("Parsed JSON [" + parsedJson.jsonString() + "] with the JSON path [" + jsonPathString + "] is not empty!"); 203 | } 204 | return this; 205 | } 206 | 207 | @Override 208 | public JsonVerifiable value() { 209 | ReadyToCheckAsserter readyToCheckAsserter = new ReadyToCheckAsserter(parsedJson, 210 | jsonPathBuffer, fieldName, jsonAsserterConfiguration); 211 | readyToCheckAsserter.checkBufferedJsonPathString(); 212 | return readyToCheckAsserter; 213 | } 214 | 215 | @Override 216 | public JsonVerifiable withoutThrowingException() { 217 | jsonAsserterConfiguration.ignoreJsonPathException = true; 218 | return this; 219 | } 220 | 221 | private JSONArray check(String jsonPathString) { 222 | if (jsonAsserterConfiguration.ignoreJsonPathException) { 223 | logOverridingWarning(); 224 | return null; 225 | } 226 | JSONArray array = jsonPathToArray(jsonPathString); 227 | if (array.isEmpty() || containsEmptyElementsOnly(array)) { 228 | throw new IllegalStateException("Parsed JSON [" + parsedJson.jsonString() + "] doesn't match the JSON path [" + jsonPathString + "]"); 229 | } 230 | return array; 231 | } 232 | 233 | private boolean containsEmptyElementsOnly(JSONArray array) { 234 | boolean empty = true; 235 | for (Object o : array) { 236 | empty = empty && isObjectEmpty(o); 237 | } 238 | return empty; 239 | } 240 | 241 | private boolean isObjectEmpty(Object o) { 242 | if (o instanceof Map) { 243 | return ((Map) o).isEmpty(); 244 | } else if (o instanceof JSONArray) { 245 | return containsEmptyElementsOnly((JSONArray) o); 246 | } else if (o instanceof List) { 247 | return ((List) o).isEmpty(); 248 | } else { 249 | return o == null; 250 | } 251 | } 252 | 253 | private JSONArray jsonPathToArray(String jsonPathString) { 254 | return parseJsonPathAsArray(jsonPathString); 255 | } 256 | 257 | private JSONArray parseJsonPathAsArray(String jsonPathString) { 258 | try { 259 | return parsedJson.read(jsonPathString, JSONArray.class); 260 | } catch (Exception e) { 261 | log.error("Exception occurred while trying to match JSON Path [{}]", jsonPathString, e); 262 | throw new RuntimeException(e); 263 | } 264 | } 265 | 266 | JSONArray checkBufferedJsonPathString() { 267 | return check(createJsonPathString()); 268 | } 269 | 270 | private String createJsonPathString() { 271 | LinkedList queue = new LinkedList(jsonPathBuffer); 272 | StringBuilder stringBuffer = new StringBuilder(); 273 | while (!queue.isEmpty()) { 274 | stringBuffer.append(queue.remove()); 275 | } 276 | return stringBuffer.toString(); 277 | } 278 | 279 | @Override 280 | public String jsonPath() { 281 | return createJsonPathString(); 282 | } 283 | 284 | @Override 285 | public void matchesJsonPath(String jsonPath) { 286 | check(jsonPath); 287 | } 288 | 289 | @Override 290 | public JsonVerifiable hasSize(int size) { 291 | if (jsonAsserterConfiguration.ignoreJsonPathException) { 292 | logOverridingWarning(); 293 | return this; 294 | } 295 | JSONArray array = checkBufferedJsonPathString(); 296 | if (array == null || array.size() != size) { 297 | throw new IllegalStateException("Parsed JSON <" + parsedJson.jsonString() + "> doesn't have the " 298 | + "size <" + size + "> for JSON path <" + createJsonPathString()+ ">. The size is <" + (array == null ? null : array.size()) + ">"); 299 | } 300 | return this; 301 | } 302 | 303 | private void logOverridingWarning() { 304 | log.trace("WARNING!!! Overriding verification of the JSON Path. Your tests may pass even though they shouldn't"); 305 | } 306 | 307 | @Override 308 | public boolean equals(Object o) { 309 | if (this == o) return true; 310 | if (o == null || getClass() != o.getClass()) return false; 311 | JsonAsserter that = (JsonAsserter) o; 312 | if (jsonPathBuffer != null ? !jsonPathBuffer.equals(that.jsonPathBuffer) : that.jsonPathBuffer != null) 313 | return false; 314 | return fieldName != null ? fieldName.equals(that.fieldName) : that.fieldName == null; 315 | 316 | } 317 | 318 | @Override 319 | public int hashCode() { 320 | int result = jsonPathBuffer != null ? jsonPathBuffer.hashCode() : 0; 321 | result = 31 * result + (fieldName != null ? fieldName.hashCode() : 0); 322 | return result; 323 | } 324 | 325 | @Override 326 | public String toString() { 327 | return "\\nAsserter{\n " + "jsonPathBuffer=" + String.valueOf(jsonPathBuffer) 328 | + "\n}"; 329 | } 330 | 331 | @Override 332 | public boolean isIteratingOverNamelessArray() { 333 | return false; 334 | } 335 | 336 | @Override 337 | public boolean isIteratingOverArray() { 338 | return false; 339 | } 340 | 341 | @Override 342 | public boolean isAssertingAValueInArray() { 343 | return false; 344 | } 345 | 346 | protected static String stringWithEscapedSingleQuotes(Object object) { 347 | String stringValue = object.toString(); 348 | return stringValue.replaceAll("'", "\\\\'"); 349 | } 350 | 351 | protected static String stringWithEscapedSingleQuotesForRegex(Object object) { 352 | return stringWithEscapedSingleQuotes(object).replace("/", "\\/"); 353 | } 354 | 355 | protected String wrapValueWithSingleQuotes(Object value) { 356 | return value instanceof String ? 357 | "'" + stringWithEscapedSingleQuotes(value) + "'" : 358 | value.toString(); 359 | } 360 | 361 | @Override 362 | @SuppressWarnings("unchecked") 363 | public T read(Class clazz) { 364 | Object readObject = parsedJson.read(jsonPath()); 365 | if (readObject instanceof JSONArray) { 366 | JSONArray array = parsedJson.read(jsonPath()); 367 | if (array.size() == 1) { 368 | return (T) array.get(0); 369 | } 370 | return (T) array; 371 | } 372 | return (T) readObject; 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/JsonAsserterConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | /** 4 | * @author Marcin Grzejszczak 5 | */ 6 | class JsonAsserterConfiguration { 7 | boolean ignoreJsonPathException = false; 8 | } 9 | -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/JsonAssertion.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | import com.jayway.jsonpath.JsonPath; 5 | 6 | import java.util.LinkedList; 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * Entry point for assertions. Use the static factory method and you're ready to go! 12 | * 13 | * @author Marcin Grzejszczak 14 | * @since 0.2.0 15 | * 16 | * @see JsonVerifiable 17 | */ 18 | public class JsonAssertion { 19 | private final DocumentContext parsedJson; 20 | private final LinkedList jsonPathBuffer = new LinkedList(); 21 | private final JsonAsserterConfiguration jsonAsserterConfiguration = new JsonAsserterConfiguration(); 22 | private static final Map CACHE = new ConcurrentHashMap(); 23 | 24 | private JsonAssertion(DocumentContext parsedJson) { 25 | this.parsedJson = parsedJson; 26 | } 27 | 28 | private JsonAssertion(String body) { 29 | DocumentContext documentContext = CACHE.get(body); 30 | if (documentContext == null && !empty(body)) { 31 | documentContext = JsonPath.parse(body); 32 | CACHE.put(body, documentContext); 33 | } 34 | this.parsedJson = documentContext; 35 | } 36 | 37 | private boolean empty(String text) { 38 | return text == null || text.length() == 0 || text.matches("^\\s*$"); 39 | } 40 | 41 | /** 42 | * Starts assertions for the JSON provided as {@code String} 43 | */ 44 | public static JsonVerifiable assertThat(String body) { 45 | return new JsonAssertion(body).root(); 46 | } 47 | 48 | /** 49 | * Starts assertions for the JSON provided as {@code DocumentContext} 50 | */ 51 | public static JsonVerifiable assertThat(DocumentContext parsedJson) { 52 | return new JsonAssertion(parsedJson).root(); 53 | } 54 | 55 | /** 56 | * Helper method so that there are no clashes with other static methods of that name 57 | * 58 | * @see JsonAssertion#assertThat(String) 59 | */ 60 | public static JsonVerifiable assertThatJson(String body) { 61 | return assertThat(body); 62 | } 63 | 64 | /** 65 | * Helper method so that there are no clashes with other static methods of that name 66 | * 67 | * @see JsonAssertion#assertThat(DocumentContext) 68 | */ 69 | public static JsonVerifiable assertThatJson(DocumentContext parsedJson) { 70 | return assertThat(parsedJson); 71 | } 72 | 73 | private JsonVerifiable root() { 74 | NamelessArrayHavingFieldAssertion asserter = new NamelessArrayHavingFieldAssertion(parsedJson, jsonPathBuffer, "", jsonAsserterConfiguration); 75 | asserter.jsonPathBuffer.offer("$"); 76 | return asserter; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/JsonPath.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | /** 4 | * Builder of JSON Paths. 5 | * 6 | * @author Marcin Grzejszczak 7 | * @since 0.3.0 8 | * 9 | * @see JsonVerifiable 10 | * @see JsonAssertion 11 | */ 12 | public class JsonPath { 13 | 14 | /** 15 | * Returns a builder of {@link JsonVerifiable} with which you can build your 16 | * JSON Path. Once finished just call {@link JsonVerifiable#jsonPath()} to get 17 | * JSON Path as String. 18 | */ 19 | public static JsonVerifiable builder() { 20 | return JsonAssertion.assertThat("").withoutThrowingException(); 21 | } 22 | 23 | /** 24 | * Using a JSON Path builder for the given JSON you can read its value. 25 | */ 26 | public static JsonVerifiable builder(String json) { 27 | return JsonAssertion.assertThat(json).withoutThrowingException(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/JsonReader.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | /** 4 | * Contract to read the value from a JSON basing on it. 5 | * 6 | * @author Marcin Grzejszczak 7 | * 8 | * @since 0.4.0 9 | */ 10 | public interface JsonReader { 11 | 12 | /** 13 | * Returns the value from the JSON, based on the created JSON Path. If the result is an 14 | * JSON Array and has a single value then that value is returned. If that's an array with 15 | * greater number of results then that array is returned. 16 | */ 17 | T read(Class clazz); 18 | } 19 | -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/JsonVerifiable.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | /** 4 | * Contract to match a parsed JSON via JSON Path 5 | * 6 | * @author Marcin Grzejszczak 7 | * 8 | * @since 0.1.0 9 | */ 10 | public interface JsonVerifiable extends IteratingOverArray, JsonReader { 11 | 12 | /** 13 | * Assertion of a field inside an array. Use it only for assertion and not traversing 14 | */ 15 | JsonVerifiable contains(Object value); 16 | 17 | /** 18 | * Field assertion. Adds a JSON Path entry for the given field. 19 | */ 20 | JsonVerifiable field(Object value); 21 | 22 | /** 23 | * Field assertions. Traverses through the list of fields and 24 | * adds a JSON Path entry for each one. 25 | */ 26 | JsonVerifiable field(String... fields); 27 | 28 | /** 29 | * When you want to assert values in a array with a given name 30 | */ 31 | JsonVerifiable array(Object value); 32 | 33 | /** 34 | * When you want to compare values of a field in a nameless array 35 | */ 36 | JsonVerifiable arrayField(); 37 | 38 | /** 39 | * When in JSON path you iterate over a nameless array 40 | */ 41 | JsonVerifiable array(); 42 | 43 | /** 44 | * Provides the {@link JsonVerifiable} for the {@code index} element of the array 45 | */ 46 | JsonVerifiable elementWithIndex(int index); 47 | 48 | /** 49 | * 0Equality comparison with String 50 | * 51 | * @throws IllegalStateException - if JSON Path is not matched for the parsed JSON 52 | */ 53 | JsonVerifiable isEqualTo(String value) throws IllegalStateException; 54 | 55 | /** 56 | * Equality comparison with any object 57 | * 58 | * @throws IllegalStateException - if JSON Path is not matched for the parsed JSON 59 | */ 60 | JsonVerifiable isEqualTo(Object value) throws IllegalStateException; 61 | 62 | /** 63 | * Equality comparison with a Number 64 | * 65 | * @throws IllegalStateException - if JSON Path is not matched for the parsed JSON 66 | */ 67 | JsonVerifiable isEqualTo(Number value) throws IllegalStateException; 68 | 69 | /** 70 | * Equality comparison to null 71 | * 72 | * @throws IllegalStateException - if JSON Path is not matched for the parsed JSON 73 | */ 74 | JsonVerifiable isNull() throws IllegalStateException; 75 | 76 | /** 77 | * Regex matching for strings 78 | * 79 | * @throws IllegalStateException - if JSON Path is not matched for the parsed JSON 80 | */ 81 | JsonVerifiable matches(String value) throws IllegalStateException; 82 | 83 | /** 84 | * Equality comparison with a Boolean 85 | * 86 | * @throws IllegalStateException - if JSON Path is not matched for the parsed JSON 87 | */ 88 | JsonVerifiable isEqualTo(Boolean value) throws IllegalStateException; 89 | 90 | /** 91 | * Checks if the array is empty 92 | * 93 | * @throws IllegalStateException - if the result of the JSON Path contains any values 94 | */ 95 | JsonVerifiable isEmpty() throws IllegalStateException; 96 | 97 | /** 98 | * Checks if instance is the same type 99 | * 100 | * @throws IllegalStateException - if the object from JSON is not of proper type 101 | */ 102 | JsonVerifiable isInstanceOf(Class clazz) throws IllegalStateException; 103 | 104 | /** 105 | * Syntactic sugar for checking against an array of primitives 106 | */ 107 | JsonVerifiable value(); 108 | 109 | /** 110 | * Calling this method will setup the fluent interface to ignore any JSON Path verification 111 | */ 112 | JsonVerifiable withoutThrowingException(); 113 | 114 | /** 115 | * Returns current JSON Path expression 116 | */ 117 | String jsonPath(); 118 | 119 | /** 120 | * Checks if the parsed document matches given JSON Path 121 | */ 122 | void matchesJsonPath(String jsonPath); 123 | 124 | /** 125 | * Checks if the array is of a given size. Example of usage: 126 | * 127 | *
{@code
128 |      * String json =  '''{ "some_list" : ["name1", "name2"] }'''
129 |      * assertThat(json).array("some_list").hasSize(2)
130 |      * }
131 | * 132 | */ 133 | JsonVerifiable hasSize(int size); 134 | 135 | } 136 | -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/NamelessArrayHavingFieldAssertion.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | 5 | import java.util.LinkedList; 6 | 7 | class NamelessArrayHavingFieldAssertion extends FieldAssertion { 8 | protected NamelessArrayHavingFieldAssertion(DocumentContext parsedJson, 9 | LinkedList jsonPathBuffer, Object fieldName, JsonAsserterConfiguration jsonAsserterConfiguration) { 10 | super(parsedJson, jsonPathBuffer, fieldName, jsonAsserterConfiguration); 11 | } 12 | 13 | @Override 14 | public boolean isIteratingOverNamelessArray() { 15 | return true; 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /jsonassert/src/main/java/com/toomuchcoding/jsonassert/ReadyToCheckAsserter.java: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | 5 | import java.util.LinkedList; 6 | 7 | class ReadyToCheckAsserter extends JsonAsserter { 8 | 9 | public ReadyToCheckAsserter(DocumentContext parsedJson, LinkedList jsonPathBuffer, 10 | Object fieldName, JsonAsserterConfiguration jsonAsserterConfiguration) { 11 | super(parsedJson, jsonPathBuffer, fieldName, jsonAsserterConfiguration); 12 | } 13 | } -------------------------------------------------------------------------------- /jsonassert/src/test/groovy/com/toomuchcoding/jsonassert/JsonAssertionSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert 2 | 3 | import com.jayway.jsonpath.DocumentContext 4 | import com.jayway.jsonpath.JsonPath 5 | import com.jayway.jsonpath.PathNotFoundException 6 | import net.minidev.json.JSONArray 7 | import spock.lang.Issue 8 | import spock.lang.Shared 9 | import spock.lang.Specification 10 | import spock.lang.Unroll 11 | 12 | import static JsonAssertion.assertThat 13 | import static JsonAssertion.assertThatJson 14 | import static groovy.json.JsonOutput.toJson 15 | 16 | /** 17 | * @author Marcin Grzejszczak 18 | */ 19 | class JsonAssertionSpec extends Specification { 20 | 21 | @Shared String json1 = ''' 22 | { 23 | "some" : { 24 | "nested" : { 25 | "json" : "with \\"val'ue", 26 | "anothervalue": 4, 27 | "withlist" : [ 28 | { "name" :"name1"} , {"name": "name2"} 29 | ] 30 | } 31 | } 32 | } 33 | ''' 34 | 35 | @Unroll 36 | def 'should convert a json with a map as root to a map of path to value '() { 37 | expect: 38 | verifiable.jsonPath() == expectedJsonPath 39 | and: 40 | JsonPath.parse(json1).read(expectedJsonPath, JSONArray) 41 | where: 42 | verifiable || expectedJsonPath 43 | assertThat(json1).field("some").field("nested").field("anothervalue").isEqualTo(4) || '''$.some.nested[?(@.anothervalue == 4)]''' 44 | assertThat(json1).field("some").field("nested").field("anothervalue") || '''$.some.nested.anothervalue''' 45 | assertThatJson(json1).field("some").field("nested").field("anothervalue").isEqualTo(4) || '''$.some.nested[?(@.anothervalue == 4)]''' 46 | assertThat(json1).field("some").field("nested").array("withlist").contains("name").isEqualTo("name1") || '''$.some.nested.withlist[*][?(@.name == 'name1')]''' 47 | assertThat(json1).field("some").field("nested").array("withlist").contains("name").isEqualTo("name2") || '''$.some.nested.withlist[*][?(@.name == 'name2')]''' 48 | assertThat(json1).field("some").field("nested").field("json").isEqualTo("with \"val'ue") || '''$.some.nested[?(@.json == 'with "val\\'ue')]''' 49 | assertThat(json1).field("some", "nested", "json").isEqualTo("with \"val'ue") || '''$.some.nested[?(@.json == 'with "val\\'ue')]''' 50 | } 51 | 52 | @Shared String json2 = '''{ 53 | "property1": "a", 54 | "property2": "b" 55 | }''' 56 | 57 | @Unroll 58 | def "should generate assertions for simple response body"() { 59 | expect: 60 | verifiable.jsonPath() == expectedJsonPath 61 | and: 62 | JsonPath.parse(json2).read(expectedJsonPath, JSONArray) 63 | where: 64 | verifiable || expectedJsonPath 65 | assertThat(json2).field("property1").isEqualTo("a") || '''$[?(@.property1 == 'a')]''' 66 | assertThat(json2).field("property2").isEqualTo("b") || '''$[?(@.property2 == 'b')]''' 67 | } 68 | 69 | @Shared String json3 = '''{ 70 | "property1": "true", 71 | "property2": null, 72 | "property3": false 73 | }''' 74 | 75 | @Unroll 76 | def "should generate assertions for null and boolean values"() { 77 | expect: 78 | verifiable.jsonPath() == expectedJsonPath 79 | and: 80 | JsonPath.parse(json3).read(expectedJsonPath, JSONArray) 81 | where: 82 | verifiable || expectedJsonPath 83 | assertThat(json3).field("property1").isEqualTo("true") || '''$[?(@.property1 == 'true')]''' 84 | assertThat(json3).field("property2").isNull() || '''$[?(@.property2 == null)]''' 85 | assertThat(json3).field("property3").isEqualTo(false) || '''$[?(@.property3 == false)]''' 86 | } 87 | 88 | @Shared Map json4 = [ 89 | property1: 'a', 90 | property2: [ 91 | [a: 'sth'], 92 | [b: 'sthElse'] 93 | ] 94 | ] 95 | 96 | @Unroll 97 | def "should generate assertions for simple response body constructed from map with a list"() { 98 | expect: 99 | verifiable.jsonPath() == expectedJsonPath 100 | and: 101 | JsonPath.parse(json4).read(expectedJsonPath, JSONArray) 102 | where: 103 | verifiable || expectedJsonPath 104 | assertThat(toJson(json4)).field("property1").isEqualTo("a") || '''$[?(@.property1 == 'a')]''' 105 | assertThat(toJson(json4)).array("property2").contains("a").isEqualTo("sth") || '''$.property2[*][?(@.a == 'sth')]''' 106 | assertThat(toJson(json4)).array("property2").contains("b").isEqualTo("sthElse") || '''$.property2[*][?(@.b == 'sthElse')]''' 107 | } 108 | 109 | @Shared Map json5 = [ 110 | property: [ 111 | 14: 0.0, 112 | 7 : 0.0 113 | ] 114 | ] 115 | 116 | @Unroll 117 | def "should generate assertions for a response body containing map with integers as keys"() { 118 | expect: 119 | verifiable.jsonPath() == expectedJsonPath 120 | and: 121 | JsonPath.parse(toJson(json5)).read(expectedJsonPath, JSONArray) 122 | where: 123 | verifiable || expectedJsonPath 124 | assertThat(toJson(json5)).field("property").field(7).isEqualTo(0.0) || '''$.property[?(@.7 == 0.0)]''' 125 | assertThat(toJson(json5)).field("property").field(14).isEqualTo(0.0) || '''$.property[?(@.14 == 0.0)]''' 126 | } 127 | 128 | @Shared String json6 = '''[ 129 | { 130 | "property1": "a" 131 | }, 132 | { 133 | "property2": "b" 134 | }]''' 135 | 136 | @Unroll 137 | def "should generate assertions for array in response body"() { 138 | expect: 139 | verifiable.jsonPath() == expectedJsonPath 140 | and: 141 | JsonPath.parse(json6).read(expectedJsonPath, JSONArray) 142 | where: 143 | verifiable || expectedJsonPath 144 | assertThat(json6).array().contains("property1").isEqualTo("a") || '''$[*][?(@.property1 == 'a')]''' 145 | assertThat(json6).array().contains("property2").isEqualTo("b") || '''$[*][?(@.property2 == 'b')]''' 146 | } 147 | 148 | @Shared String json7 = '''{ 149 | "property1": [ 150 | { "property2": "test1"}, 151 | { "property3": "test2"} 152 | ] 153 | }''' 154 | 155 | @Unroll 156 | def "should generate assertions for array inside response body element"() { 157 | expect: 158 | verifiable.jsonPath() == expectedJsonPath 159 | and: 160 | JsonPath.parse(json7).read(expectedJsonPath, JSONArray) 161 | where: 162 | verifiable || expectedJsonPath 163 | assertThat(json7).array("property1").contains("property2").isEqualTo("test1") || '''$.property1[*][?(@.property2 == 'test1')]''' 164 | assertThat(json7).array("property1").contains("property3").isEqualTo("test2") || '''$.property1[*][?(@.property3 == 'test2')]''' 165 | } 166 | 167 | @Shared String json8 = """{ 168 | "property1": "a", 169 | "property2": {"property3": "b"} 170 | }""" 171 | 172 | def "should generate assertions for nested objects in response body"() { 173 | expect: 174 | verifiable.jsonPath() == expectedJsonPath 175 | and: 176 | JsonPath.parse(json8).read(expectedJsonPath, JSONArray) 177 | where: 178 | verifiable || expectedJsonPath 179 | assertThat(json8).field("property2").field("property3").isEqualTo("b") || '''$.property2[?(@.property3 == 'b')]''' 180 | assertThat(json8).field("property1").isEqualTo("a") || '''$[?(@.property1 == 'a')]''' 181 | } 182 | 183 | @Shared Map json9 = [ 184 | property1: "a", 185 | property2: 123 186 | ] 187 | 188 | @Unroll 189 | def "should generate regex assertions for map objects in response body"() { 190 | expect: 191 | verifiable.jsonPath() == expectedJsonPath 192 | and: 193 | JsonPath.parse(json9).read(expectedJsonPath, JSONArray) 194 | where: 195 | verifiable || expectedJsonPath 196 | assertThat(toJson(json9)).field("property2").matches("[0-9]{3}") || '''$[?(@.property2 =~ /[0-9]{3}/)]''' 197 | assertThat(toJson(json9)).field("property1").isEqualTo("a") || '''$[?(@.property1 == 'a')]''' 198 | } 199 | 200 | def "should generate escaped regex assertions for string objects in response body"() { 201 | given: 202 | Map json = [ 203 | property2: 123123 204 | ] 205 | expect: 206 | def verifiable = assertThat(toJson(json)).field("property2").matches("\\d+") 207 | verifiable.jsonPath() == '''$[?(@.property2 =~ /\\d+/)]''' 208 | and: 209 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 210 | } 211 | 212 | @Shared Map json10 = [ 213 | errors: [ 214 | [property: "bank_account_number", 215 | message: "incorrect_format"] 216 | ] 217 | ] 218 | 219 | @Unroll 220 | def "should work with more complex stuff and jsonpaths"() { 221 | expect: 222 | verifiable.jsonPath() == expectedJsonPath 223 | and: 224 | JsonPath.parse(json10).read(expectedJsonPath, JSONArray) 225 | where: 226 | verifiable || expectedJsonPath 227 | assertThat(toJson(json10)).array("errors").contains("property").isEqualTo("bank_account_number") || '''$.errors[*][?(@.property == 'bank_account_number')]''' 228 | assertThat(toJson(json10)).array("errors").contains("message").isEqualTo("incorrect_format") || '''$.errors[*][?(@.message == 'incorrect_format')]''' 229 | } 230 | 231 | @Shared String json11 = ''' 232 | [{ 233 | "place": 234 | { 235 | "bounding_box": 236 | { 237 | "coordinates": 238 | [[ 239 | [-77.119759,38.995548], 240 | [-76.909393,38.791645] 241 | ]] 242 | } 243 | } 244 | }] 245 | ''' 246 | 247 | @Unroll 248 | def "should manage to parse a triple array"() { 249 | expect: 250 | verifiable.jsonPath() == expectedJsonPath 251 | and: 252 | JsonPath.parse(json11).read(expectedJsonPath, JSONArray) 253 | where: 254 | verifiable || expectedJsonPath 255 | assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(38.995548).value() || '''$[*].place.bounding_box.coordinates[*][*][?(@ == 38.995548)]''' 256 | assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(-77.119759).value() || '''$[*].place.bounding_box.coordinates[*][*][?(@ == -77.119759)]''' 257 | assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(-76.909393).value() || '''$[*].place.bounding_box.coordinates[*][*][?(@ == -76.909393)]''' 258 | assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(38.791645).value() || '''$[*].place.bounding_box.coordinates[*][*][?(@ == 38.791645)]''' 259 | 260 | } 261 | 262 | @Shared String json12 = ''' 263 | [{ 264 | "place": 265 | { 266 | "bounding_box": 267 | { 268 | "coordinates": 269 | [[[ 270 | [-77.119759,38.995548], 271 | [-76.909393,38.791645] 272 | ]]] 273 | } 274 | } 275 | }] 276 | ''' 277 | 278 | @Unroll 279 | def "should manage to parse a quadriple array"() { 280 | expect: 281 | verifiable.jsonPath() == expectedJsonPath 282 | and: 283 | JsonPath.parse(json12).read(expectedJsonPath, JSONArray) 284 | where: 285 | verifiable || expectedJsonPath 286 | assertThat(json12).array().field("place").field("bounding_box").array("coordinates").array().array().array().arrayField().contains(38.995548).value() || '''$[*].place.bounding_box.coordinates[*][*][*][?(@ == 38.995548)]''' 287 | assertThat(json12).array().field("place").field("bounding_box").array("coordinates").array().array().array().arrayField().contains(-77.119759).value() || '''$[*].place.bounding_box.coordinates[*][*][*][?(@ == -77.119759)]''' 288 | assertThat(json12).array().field("place").field("bounding_box").array("coordinates").array().array().array().arrayField().contains(-76.909393).value() || '''$[*].place.bounding_box.coordinates[*][*][*][?(@ == -76.909393)]''' 289 | assertThat(json12).array().field("place").field("bounding_box").array("coordinates").array().array().array().arrayField().contains(38.791645).value() || '''$[*].place.bounding_box.coordinates[*][*][*][?(@ == 38.791645)]''' 290 | 291 | } 292 | 293 | @Shared String jsonArrayInArray= ''' 294 | { 295 | "coordinates": 296 | [ 297 | [-77.119759,38.995548], 298 | [-76.909393,38.791645] 299 | ] 300 | } 301 | } 302 | ''' 303 | 304 | @Unroll 305 | def "should manage to parse array in array"() { 306 | expect: 307 | verifiable.jsonPath() == expectedJsonPath 308 | and: 309 | JsonPath.parse(jsonArrayInArray).read(expectedJsonPath, JSONArray) 310 | where: 311 | verifiable || expectedJsonPath 312 | assertThat(jsonArrayInArray).array("coordinates").array().arrayField().contains(38.995548).value() || '''$.coordinates[*][?(@ == 38.995548)]''' 313 | assertThat(jsonArrayInArray).array("coordinates").array().arrayField().contains(-77.119759).value() || '''$.coordinates[*][?(@ == -77.119759)]''' 314 | assertThat(jsonArrayInArray).array("coordinates").array().arrayField().contains(-76.909393).value() || '''$.coordinates[*][?(@ == -76.909393)]''' 315 | assertThat(jsonArrayInArray).array("coordinates").array().arrayField().contains(38.791645).value() || '''$.coordinates[*][?(@ == 38.791645)]''' 316 | } 317 | 318 | @Unroll 319 | def 'should convert a json with list as root to a map of path to value'() { 320 | expect: 321 | assertThat(json).array().field("some").field("nested").field("json").isEqualTo("with value").jsonPath() == '''$[*].some.nested[?(@.json == 'with value')]''' 322 | assertThat(json).array().field("some").field("nested").field("anothervalue").isEqualTo(4).jsonPath() == '''$[*].some.nested[?(@.anothervalue == 4)]''' 323 | assertThat(json).array().field("some").field("nested").array("withlist").contains("name").isEqualTo("name1").jsonPath() == '''$[*].some.nested.withlist[*][?(@.name == 'name1')]''' 324 | assertThat(json).array().field("some").field("nested").array("withlist").contains("name").isEqualTo("name2").jsonPath() == '''$[*].some.nested.withlist[*][?(@.name == 'name2')]''' 325 | assertThat(json).array().field("some").field("nested").array("withlist").field("anothernested").field("name").isEqualTo("name3").jsonPath() == '''$[*].some.nested.withlist[*].anothernested[?(@.name == 'name3')]''' 326 | where: 327 | json << [ 328 | ''' 329 | [ { 330 | "some" : { 331 | "nested" : { 332 | "json" : "with value", 333 | "anothervalue": 4, 334 | "withlist" : [ 335 | { "name" :"name1"} , {"name": "name2"}, {"anothernested": { "name": "name3"} } 336 | ] 337 | } 338 | } 339 | }, 340 | { 341 | "someother" : { 342 | "nested" : { 343 | "json" : "with value", 344 | "anothervalue": 4, 345 | "withlist" : [ 346 | { "name" :"name1"} , {"name": "name2"} 347 | ] 348 | } 349 | } 350 | } 351 | ] 352 | ''', 353 | ''' 354 | [{ 355 | "someother" : { 356 | "nested" : { 357 | "json" : "with value", 358 | "anothervalue": 4, 359 | "withlist" : [ 360 | { "name" :"name1"} , {"name": "name2"} 361 | ] 362 | } 363 | } 364 | }, 365 | { 366 | "some" : { 367 | "nested" : { 368 | "json" : "with value", 369 | "anothervalue": 4, 370 | "withlist" : [ 371 | {"name": "name2"}, {"anothernested": { "name": "name3"} }, { "name" :"name1"} 372 | ] 373 | } 374 | } 375 | } 376 | ]'''] 377 | } 378 | 379 | def "should run json path when provided manually"() { 380 | given: 381 | String json = """{ 382 | "property1": "a", 383 | "property2": {"property3": "b"} 384 | }""" 385 | and: 386 | String jsonPath = '''$[?(@.property1 == 'a')]''' 387 | expect: 388 | assertThat(json).matchesJsonPath(jsonPath) 389 | and: 390 | JsonPath.parse(json).read(jsonPath, JSONArray) 391 | } 392 | 393 | def "should throw exception when json path is not matched"() { 394 | given: 395 | String json = """{ 396 | "property1": "a", 397 | "property2": {"property3": "b"} 398 | }""" 399 | and: 400 | String jsonPath = '''$[?(@.property1 == 'c')]''' 401 | when: 402 | assertThat(json).matchesJsonPath(jsonPath) 403 | then: 404 | IllegalStateException illegalStateException = thrown(IllegalStateException) 405 | illegalStateException.message.contains("Parsed JSON") 406 | illegalStateException.message.contains("doesn't match") 407 | } 408 | 409 | def "should not throw exception when json path is not matched and system prop overrides the check"() { 410 | given: 411 | String json = """{ 412 | "property1": "a", 413 | "property2": {"property3": "b"} 414 | }""" 415 | and: 416 | String jsonPath = '''$[?(@.property1 == 'c')]''' 417 | when: 418 | assertThat(json).withoutThrowingException().matchesJsonPath(jsonPath) 419 | then: 420 | noExceptionThrown() 421 | } 422 | 423 | def "should generate escaped regex assertions for boolean objects in response body"() { 424 | given: 425 | Map json = [ 426 | property2: true 427 | ] 428 | expect: 429 | def verifiable = assertThat(toJson(json)).field("property2").matches('true|false') 430 | verifiable.jsonPath() == '''$[?(@.property2 =~ /true|false/)]''' 431 | and: 432 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 433 | } 434 | 435 | def "should generate escaped regex assertions for numbers objects in response body"() { 436 | given: 437 | Map json = [ 438 | property2: 50 439 | ] 440 | expect: 441 | def verifiable = assertThat(toJson(json)).field("property2").matches('[0-9]{2}') 442 | verifiable.jsonPath() == '''$[?(@.property2 =~ /[0-9]{2}/)]''' 443 | and: 444 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 445 | } 446 | 447 | def "should escape regular expression properly"() { 448 | given: 449 | String json = """ 450 | { 451 | "path" : "/api/12", 452 | "correlationId" : 123456 453 | } 454 | """ 455 | expect: 456 | DocumentContext parsedJson = JsonPath.parse(json) 457 | def verifiable = assertThatJson(parsedJson).field("path").matches("^/api/[0-9]{2}\$") 458 | verifiable.jsonPath() == '''$[?(@.path =~ /^\\/api\\/[0-9]{2}$/)]''' 459 | and: 460 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 461 | } 462 | 463 | @Issue("Accurest#193") 464 | def "should escape single quotes in a quoted string"() { 465 | given: 466 | String json = """ 467 | { 468 | "text" : "text with 'quotes' inside" 469 | } 470 | """ 471 | expect: 472 | DocumentContext parsedJson = JsonPath.parse(json) 473 | def verifiable = assertThatJson(parsedJson).field("text").isEqualTo("text with 'quotes' inside") 474 | verifiable.jsonPath() == '''$[?(@.text == 'text with \\'quotes\\' inside')]''' 475 | and: 476 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 477 | } 478 | 479 | @Issue("Accurest#193") 480 | def "should escape double quotes in a quoted string"() { 481 | given: 482 | String json = """ 483 | { 484 | "text" : "text with \\"quotes\\" inside" 485 | } 486 | """ 487 | expect: 488 | DocumentContext parsedJson = JsonPath.parse(json) 489 | def verifiable = assertThatJson(parsedJson).field("text").isEqualTo('''text with "quotes" inside''') 490 | verifiable.jsonPath() == '''$[?(@.text == 'text with "quotes" inside')]''' 491 | and: 492 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 493 | } 494 | 495 | def 'should resolve the value of JSON via JSON Path'() { 496 | given: 497 | String json = 498 | ''' 499 | [ { 500 | "some" : { 501 | "nested" : { 502 | "json" : "with value", 503 | "anothervalue": 4, 504 | "withlist" : [ 505 | { "name" :"name1"} , 506 | {"name": "name2"}, 507 | {"anothernested": { "name": "name3"} } 508 | ] 509 | } 510 | } 511 | }, 512 | { 513 | "someother" : { 514 | "nested" : { 515 | "json" : true, 516 | "anothervalue": 4, 517 | "withlist" : [ 518 | { "name" :"name1"} , {"name": "name2"} 519 | ], 520 | "withlist2" : [ 521 | "a", "b" 522 | ] 523 | } 524 | } 525 | } 526 | ] 527 | ''' 528 | expect: 529 | com.toomuchcoding.jsonassert.JsonPath.builder(json).array().field("some").field("nested").field("json").read(String) == 'with value' 530 | com.toomuchcoding.jsonassert.JsonPath.builder(json).array().field("some").field("nested").field("anothervalue").read(Integer) == 4 531 | assertThat(json).array().field("some").field("nested").array("withlist").field("name").read(List) == ['name1', 'name2'] 532 | assertThat(json).array().field("someother").field("nested").array("withlist2").read(List) == ['a', 'b'] 533 | assertThat(json).array().field("someother").field("nested").field("json").read(Boolean) == true 534 | } 535 | 536 | def 'should assert json with only top list elements'() { 537 | given: 538 | String json = '''["Java", "Java8", "Spring", "SpringBoot", "Stream"]''' 539 | expect: 540 | assertThatJson(json).arrayField().contains("Java8").value() 541 | assertThatJson(json).arrayField().contains("Spring").value() 542 | assertThatJson(json).arrayField().contains("Java").value() 543 | assertThatJson(json).arrayField().contains("Stream").value() 544 | assertThatJson(json).arrayField().contains("SpringBoot").value() 545 | } 546 | 547 | @Issue("#9") 548 | def 'should match array containing an array of primitives'() { 549 | given: 550 | String json = '''{"first_name":"existing", 551 | "partners":[ 552 | { "role":"AGENT", 553 | "payment_methods":[ "BANK", "CASH" ] 554 | } 555 | ] 556 | }''' 557 | expect: 558 | def verifiable = assertThatJson(json).array("partners").array("payment_methods").arrayField().isEqualTo("BANK").value() 559 | verifiable.jsonPath() == '''$.partners[*].payment_methods[?(@ == 'BANK')]''' 560 | and: 561 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 562 | } 563 | 564 | @Issue("#9") 565 | def 'should match pattern in array'() { 566 | given: 567 | String json = '''{ "authorities": ["ROLE_ADMIN"] }''' 568 | expect: 569 | def verifiable = assertThatJson(json).array("authorities").arrayField().matches("^[a-zA-Z0-9_\\- ]+\$").value() 570 | verifiable.jsonPath() == '''$.authorities[?(@ =~ /^[a-zA-Z0-9_\\- ]+$/)]''' 571 | and: 572 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 573 | } 574 | 575 | @Issue("#10") 576 | def 'should manage to parse array with string values'() { 577 | given: 578 | String json = '''{ "some_list" : ["name1", "name2"] }''' 579 | expect: 580 | def v1 = assertThat(JsonPath.parse(json)).array("some_list").arrayField().isEqualTo("name1") 581 | def v2 = assertThat(JsonPath.parse(json)).array("some_list").arrayField().isEqualTo("name2") 582 | and: 583 | v1.jsonPath() == '''$.some_list[?(@ == 'name1')]''' 584 | v2.jsonPath() == '''$.some_list[?(@ == 'name2')]''' 585 | and: 586 | JsonPath.parse(json).read(v1.jsonPath(), JSONArray) 587 | JsonPath.parse(json).read(v2.jsonPath(), JSONArray) 588 | } 589 | 590 | def 'should parse an array of arrays that are root elements'() { 591 | given: 592 | String json = '''[["Programming", "Java"], ["Programming", "Java", "Spring", "Boot"]]''' 593 | expect: 594 | def v1 = assertThatJson(JsonPath.parse(json)).array().arrayField().isEqualTo("Java").value() 595 | and: 596 | v1.jsonPath() == '''$[*][?(@ == 'Java')]''' 597 | and: 598 | JsonPath.parse(json).read(v1.jsonPath(), JSONArray) 599 | } 600 | 601 | @Issue('#11') 602 | def 'should allow to check array size'() { 603 | given: 604 | String json = '''{ "some_list" : ["name1", "name2"] }''' 605 | expect: 606 | assertThat(json).array("some_list").hasSize(2) 607 | when: 608 | assertThat(json).array("some_list").hasSize(5) 609 | then: 610 | def ex = thrown(RuntimeException) 611 | ex instanceof IllegalStateException 612 | ex.message == '''Parsed JSON <{"some_list":["name1","name2"]}> doesn't have the size <5> for JSON path <$.some_list[*]>. The size is <2>''' 613 | } 614 | 615 | @Issue('#11') 616 | def 'should allow to check size of root array'() { 617 | given: 618 | String json = '''["name1", "name2"]''' 619 | expect: 620 | assertThat(json).hasSize(2) 621 | when: 622 | assertThat(json).hasSize(5) 623 | then: 624 | def ex = thrown(RuntimeException) 625 | ex instanceof IllegalStateException 626 | ex.message == '''Parsed JSON <["name1","name2"]> doesn't have the size <5> for JSON path <$>. The size is <2>''' 627 | } 628 | 629 | @Issue('#11') 630 | def 'should allow to check size of a nested array'() { 631 | given: 632 | String json = 633 | ''' 634 | [ { 635 | "some" : { 636 | "nested" : { 637 | "json" : "with value", 638 | "anothervalue": 4, 639 | "withlist" : [ 640 | { "name" :"name1"} , 641 | {"name": "name2"}, 642 | {"anothernested": { "name": "name3"} } 643 | ] 644 | } 645 | } 646 | }, 647 | { 648 | "someother" : { 649 | "nested" : { 650 | "json" : true, 651 | "anothervalue": 4, 652 | "withlist" : [ 653 | { "name" :"name1"} , {"name": "name2"} 654 | ], 655 | "withlist2" : [ 656 | "a", "b" 657 | ] 658 | } 659 | } 660 | } 661 | ]''' 662 | expect: 663 | assertThat(json).array().field("someother").field("nested").array("withlist2").hasSize(2) 664 | when: 665 | assertThat(json).array().field("someother").field("nested").array("withlist2").hasSize(5) 666 | then: 667 | def ex = thrown(RuntimeException) 668 | ex instanceof IllegalStateException 669 | ex.message == '''Parsed JSON <[{"some":{"nested":{"json":"with value","anothervalue":4,"withlist":[{"name":"name1"},{"name":"name2"},{"anothernested":{"name":"name3"}}]}}},{"someother":{"nested":{"json":true,"anothervalue":4,"withlist":[{"name":"name1"},{"name":"name2"}],"withlist2":["a","b"]}}}]> doesn't have the size <5> for JSON path <$[*].someother.nested.withlist2[*]>. The size is <2>''' 670 | } 671 | 672 | @Issue('#11') 673 | def 'should allow to check size of a named array'() { 674 | given: 675 | def json = [ 676 | property1: 'a', 677 | property2: [ 678 | [a: 'sth'], 679 | [b: 'sthElse'] 680 | ] 681 | ] 682 | expect: 683 | assertThat(toJson(json)).array("property2").hasSize(2) 684 | when: 685 | assertThat(toJson(json)).array("property2").hasSize(5) 686 | then: 687 | def ex = thrown(RuntimeException) 688 | ex instanceof IllegalStateException 689 | ex.message == '''Parsed JSON <{"property1":"a","property2":[{"a":"sth"},{"b":"sthElse"}]}> doesn't have the size <5> for JSON path <$.property2[*]>. The size is <2>''' 690 | } 691 | 692 | @Issue('#11') 693 | def 'should allow to check size of two nameless arrays'() { 694 | given: 695 | String json = '''[["Programming", "Java"], ["Programming", "Java", "Spring", "Boot"], ["Programming", "Java", "Spring", "Boot", "Master"]]''' 696 | expect: 697 | assertThat(json).hasSize(3) 698 | assertThat(json).elementWithIndex(0).hasSize(2) 699 | assertThat(json).elementWithIndex(1).hasSize(4) 700 | when: 701 | assertThat(json).array().hasSize(4) 702 | then: 703 | def ex = thrown(RuntimeException) 704 | ex instanceof IllegalStateException 705 | ex.message == '''Parsed JSON <[["Programming","Java"],["Programming","Java","Spring","Boot"],["Programming","Java","Spring","Boot","Master"]]> doesn't have the size <4> for JSON path <$[*]>. The size is <3>''' 706 | } 707 | 708 | @Issue('#11') 709 | def 'should allow to check size of two nameless arrays in a nameless array'() { 710 | given: 711 | String json = '''[[["Programming", "Java"], ["Programming", "Java", "Spring", "Boot"]]]''' 712 | expect: 713 | assertThat(json).hasSize(1) 714 | assertThat(json).elementWithIndex(0).elementWithIndex(0).hasSize(2) 715 | assertThat(json).elementWithIndex(0).elementWithIndex(1).hasSize(4) 716 | when: 717 | assertThat(json).elementWithIndex(0).elementWithIndex(1).hasSize(5) 718 | then: 719 | def ex = thrown(RuntimeException) 720 | ex instanceof IllegalStateException 721 | ex.message == '''Parsed JSON <[[["Programming","Java"],["Programming","Java","Spring","Boot"]]]> doesn't have the size <5> for JSON path <$[0][1]>. The size is <4>''' 722 | } 723 | 724 | @Issue('#11') 725 | def 'should allow to check array size of nameless array'() { 726 | given: 727 | String json = '''{ "coordinates" : [[ 728 | ["name1", "name2"], 729 | ["name3", "name4"] 730 | ]] }''' 731 | expect: 732 | assertThat(json).array("coordinates").array().hasSize(2) 733 | when: 734 | assertThat(json).array("coordinates").array().hasSize(5) 735 | then: 736 | def ex = thrown(RuntimeException) 737 | ex instanceof IllegalStateException 738 | ex.message == '''Parsed JSON <{"coordinates":[[["name1","name2"],["name3","name4"]]]}> doesn't have the size <5> for JSON path <$.coordinates[*][*]>. The size is <2>''' 739 | } 740 | 741 | @Unroll 742 | def 'should fail on non existent path'() { 743 | when: 744 | assertThat(json instanceof Map ? toJson(json) : json).field("non").field("existant").field("field").isEqualTo("imaginary value") 745 | then: 746 | def ex = thrown(RuntimeException) 747 | ex.cause instanceof PathNotFoundException 748 | where: 749 | json << [ json1, json2, json3, json4, json5, json6, json7, json8, json9, json10, json11 ] 750 | } 751 | 752 | @Issue('#14') 753 | def 'should allow to check if size is empty'() { 754 | given: 755 | String json = '''{ "coordinates" : [], "foo": ["bar", "baz"] }''' 756 | expect: 757 | assertThat(json).array("coordinates").isEmpty() 758 | when: 759 | assertThat(json).array("foo").isEmpty() 760 | then: 761 | def ex = thrown(RuntimeException) 762 | ex instanceof IllegalStateException 763 | ex.message == '''Parsed JSON [{"coordinates":[],"foo":["bar","baz"]}] with the JSON path [$.foo[*]] is not empty!''' 764 | } 765 | 766 | @Issue('#17') 767 | def 'should ignore exception on empty check'() { 768 | given: 769 | String json = '''{ "coordinates" : [], "foo": ["bar", "baz"] }''' 770 | when: 771 | assertThat(json).withoutThrowingException().array("foo").isEmpty() 772 | then: 773 | noExceptionThrown() 774 | } 775 | 776 | @Issue('#16') 777 | def 'should throw exception when an empty array is returned'() { 778 | given: 779 | String json = '''{}''' 780 | when: 781 | assertThatJson(json).field("doesNotExist").matches("[\\p{L}]*") 782 | then: 783 | def ex = thrown(RuntimeException) 784 | ex instanceof IllegalStateException 785 | ex.message == '''Parsed JSON [{}] doesn't match the JSON path [$[?(@.doesNotExist =~ /[\\p{L}]*/)]]''' 786 | when: 787 | assertThatJson(json).array("c").matches("[\\p{L}]*") 788 | then: 789 | ex = thrown(RuntimeException) 790 | ex instanceof IllegalStateException 791 | ex.message == '''Parsed JSON [{}] doesn't match the JSON path [$[?(@.c =~ /[\\p{L}]*/)]]''' 792 | } 793 | 794 | @Issue('#18') 795 | def 'should read types of objects'() { 796 | given: 797 | String json = '''{ "foo": 46 }''' 798 | when: 799 | assertThatJson(json).field("foo").isInstanceOf(Number) 800 | then: 801 | noExceptionThrown() 802 | when: 803 | assertThatJson(json).field("foo").isInstanceOf(String) 804 | then: 805 | RuntimeException ex = thrown(RuntimeException) 806 | ex instanceof IllegalStateException 807 | ex.message == '''For JSON path [$.foo] instance of [Integer] is not assignable from [String]''' 808 | } 809 | 810 | @Issue('#18') 811 | def 'should read big numbers'() { 812 | given: 813 | String json = '''{ "largeNum": 55534673.56, "bigInt": 2147483647, "decimals": 0.1287361923123}''' 814 | when: 815 | assertThatJson(json).field("largeNum").isEqualTo(55534673.56 as Double) 816 | then: 817 | noExceptionThrown() 818 | when: 819 | assertThatJson(json).field("bigInt").isEqualTo(Integer.MAX_VALUE as Integer) 820 | then: 821 | noExceptionThrown() 822 | when: 823 | assertThatJson(json).field("decimals").isEqualTo(0.1287361923123 as Double) 824 | then: 825 | noExceptionThrown() 826 | } 827 | 828 | @Issue('#28') 829 | def 'should match {} as empty'() { 830 | given: 831 | String json = '''{ "field1": {}, field2: [] }}''' 832 | when: 833 | assertThatJson(json).field("field1").isEmpty() 834 | then: 835 | noExceptionThrown() 836 | when: 837 | assertThatJson(json).field("field2").isEmpty() 838 | then: 839 | noExceptionThrown() 840 | } 841 | 842 | @Issue('#31') 843 | def 'should work with scientific notation'() { 844 | given: 845 | String json = '''{ "n": 1.12E-12}''' 846 | when: 847 | assertThatJson(json).field("n").isEqualTo(1.12E-12) 848 | then: 849 | noExceptionThrown() 850 | } 851 | 852 | @Issue('#57') 853 | def 'should work with matchers'() { 854 | given: 855 | String json = '''{ "value": -1}''' 856 | when: 857 | assertThatJson(json).field("['value']").matches("-?(\\d+)") 858 | then: 859 | noExceptionThrown() 860 | } 861 | 862 | } 863 | -------------------------------------------------------------------------------- /jsonassert/src/test/groovy/com/toomuchcoding/jsonassert/JsonPathSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.toomuchcoding.jsonassert 2 | 3 | import spock.lang.Specification 4 | 5 | /** 6 | * @author Marcin Grzejszczak 7 | */ 8 | class JsonPathSpec extends Specification { 9 | 10 | def "should generate proper JSON paths"() { 11 | expect: 12 | jsonPath == expectedJsonPath 13 | where: 14 | jsonPath || expectedJsonPath 15 | JsonPath.builder().field("some").field("nested").field("anothervalue").isEqualTo(4).jsonPath() || '''$.some.nested[?(@.anothervalue == 4)]''' 16 | JsonPath.builder().field("some").field("nested").field("anothervalue").isEqualTo(4).jsonPath() || '''$.some.nested[?(@.anothervalue == 4)]''' 17 | JsonPath.builder().field("some").field("nested").array("withlist").contains("name").isEqualTo("name1").jsonPath() || '''$.some.nested.withlist[*][?(@.name == 'name1')]''' 18 | JsonPath.builder().field("some").field("nested").array("withlist").contains("name").isEqualTo("name2").jsonPath() || '''$.some.nested.withlist[*][?(@.name == 'name2')]''' 19 | JsonPath.builder().field("some").field("nested").field("json").isEqualTo("with \"val'ue").jsonPath() || '''$.some.nested[?(@.json == 'with "val\\'ue')]''' 20 | JsonPath.builder().field("some", "nested", "json").isEqualTo("with \"val'ue").jsonPath() || '''$.some.nested[?(@.json == 'with "val\\'ue')]''' 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.toomuchcoding.jsonassert 6 | jsonassert-parent 7 | pom 8 | 0.8.1-SNAPSHOT 9 | 10 | Json Assert Parent 11 | Json Assert Parent 12 | https://github.com/marcingrzejszczak/jsonassert 13 | 2015 14 | 15 | 16 | 17 | Apache License, Version 2.0 18 | https://www.apache.org/licenses/LICENSE-2.0 19 | 20 | Copyright 2014-2023 the original author or authors. 21 | 22 | Licensed under the Apache License, Version 2.0 (the "License"); 23 | you may not use this file except in compliance with the License. 24 | You may obtain a copy of the License at 25 | 26 | https://www.apache.org/licenses/LICENSE-2.0 27 | 28 | Unless required by applicable law or agreed to in writing, software 29 | distributed under the License is distributed on an "AS IS" BASIS, 30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 31 | implied. 32 | 33 | See the License for the specific language governing permissions and 34 | limitations under the License. 35 | 36 | 37 | 38 | 39 | https://github.com/marcingrzejszczak/jsonassert 40 | scm:git:git://github.com/marcingrzejszczak/jsonassert.git 41 | 42 | 43 | scm:git:ssh://git@github.com/marcingrzejszczak/jsonassert.git 44 | 45 | HEAD 46 | 47 | 48 | 49 | mgrzejszczak 50 | Marcin Grzejszczak 51 | marcin at grzejszczak.pl 52 | 53 | lead 54 | 55 | 56 | 57 | 58 | https://github.com/marcingrzejszczak/jsonassert 59 | 60 | jsonassert-docs 61 | 62 | https://github.com/marcingrzejszczak/jsonassert 63 | 64 | 65 | 66 | Nexus Release Repository 67 | sonatype-nexus-staging 68 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 69 | 70 | 71 | 72 | 73 | jsonassert 74 | jsonassert-assertj-java8 75 | jsonassert-shade 76 | tests 77 | 78 | 79 | 80 | 1.8 81 | UTF-8 82 | UTF-8 83 | ${java.version} 84 | ${java.version} 85 | 86 | 2.9.0 87 | 4.0.21 88 | 2.3-groovy-4.0 89 | 3.3.0 90 | 3.4 91 | 1.5.6 92 | 2.5.1 93 | 1.7.36 94 | 95 | 1.13.1 96 | 3.3.0 97 | 3.7.0 98 | 3.3.1 99 | 1.6 100 | 101 | 102 | 103 | 104 | 105 | com.jayway.jsonpath 106 | json-path 107 | ${json-path.version} 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | maven-javadoc-plugin 117 | ${maven-javadoc-plugin.version} 118 | 119 | 120 | javadoc 121 | 122 | jar 123 | 124 | package 125 | 126 | 127 | 128 | 129 | maven-source-plugin 130 | ${maven-source-plugin.version} 131 | 132 | 133 | attach-sources 134 | 135 | jar-no-fork 136 | 137 | prepare-package 138 | 139 | 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-surefire-plugin 144 | ${maven-surefire-plugin.version} 145 | 146 | 147 | **/*Spec.* 148 | **/*Tests.* 149 | **/*Test.* 150 | 151 | plain 152 | true 153 | 154 | 155 | 156 | org.codehaus.gmavenplus 157 | gmavenplus-plugin 158 | ${gmavenplus-plugin.version} 159 | 160 | 161 | 162 | addTestSources 163 | compileTests 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | org.apache.maven.plugins 174 | maven-release-plugin 175 | 3.1.0 176 | 177 | v@{project.version} 178 | central 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | central 187 | 188 | 189 | 190 | org.apache.maven.plugins 191 | maven-gpg-plugin 192 | ${maven-gpg-plugin.version} 193 | 194 | 195 | sign-artifacts 196 | verify 197 | 198 | sign 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | 5 | export DEV_VERSION="${DEV_VERSION:?You must set the next dev version}" 6 | export RELEASE_VERSION="${RELEASE_VERSION:?You must set the next release version}" 7 | 8 | echo "[RELEASE] Will release [${RELEASE_VERSION}] and then bump to dev version [${DEV_VERSION}]" 9 | 10 | echo "[RELEASE] Fetching tags..." 11 | git fetch --tags 12 | 13 | echo "[RELEASE] Preparing for the release..." 14 | ./mvnw -B -Dtag=v"${RELEASE_VERSION}" release:clean release:prepare -DreleaseVersion="${RELEASE_VERSION}" -DdevelopmentVersion="${DEV_VERSION}" 15 | 16 | echo "[RELEASE] Doing the actual release..." 17 | ./mvnw -B -Dtag=v"${RELEASE_VERSION}" -DreleaseVersion="${RELEASE_VERSION}" -DdevelopmentVersion="${DEV_VERSION}" -Dgoals=deploy release:perform -Pcentral 18 | 19 | echo "[RELEASE] Pushing tags..." 20 | git push origin main --tags -------------------------------------------------------------------------------- /tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | jsonassert-tests 5 | 0.8.1-SNAPSHOT 6 | 7 | 8 | com.toomuchcoding.jsonassert 9 | jsonassert-parent 10 | 0.8.1-SNAPSHOT 11 | .. 12 | 13 | 14 | 15 | 16 | ${project.groupId} 17 | jsonassert-shade 18 | ${project.version} 19 | test 20 | 21 | 22 | org.apache.groovy 23 | groovy-json 24 | ${groovy.version} 25 | test 26 | 27 | 28 | org.spockframework 29 | spock-core 30 | ${spock.version} 31 | test 32 | 33 | 34 | cglib 35 | cglib-nodep 36 | ${cglib-nodep.version} 37 | test 38 | 39 | 40 | org.objenesis 41 | objenesis 42 | ${objenesis.version} 43 | test 44 | 45 | 46 | ch.qos.logback 47 | logback-classic 48 | ${logback-classic.version} 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.codehaus.gmavenplus 57 | gmavenplus-plugin 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-surefire-plugin 62 | 63 | false 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /tests/src/test/groovy/shaded/com/toomuchcoding/jsonassert/JsonAssertionSpec.groovy: -------------------------------------------------------------------------------- 1 | package shaded.com.toomuchcoding.jsonassert 2 | 3 | import shaded.com.jayway.jsonpath.DocumentContext 4 | import shaded.com.jayway.jsonpath.JsonPath 5 | import shaded.com.jayway.jsonpath.PathNotFoundException 6 | import shaded.net.minidev.json.JSONArray 7 | import spock.lang.Issue 8 | import spock.lang.Shared 9 | import spock.lang.Specification 10 | import spock.lang.Unroll 11 | 12 | import static com.toomuchcoding.jsonassert.JsonAssertion.assertThat 13 | import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson 14 | import static groovy.json.JsonOutput.toJson 15 | 16 | /** 17 | * @author Marcin Grzejszczak 18 | */ 19 | class JsonAssertionSpec extends Specification { 20 | 21 | @Shared String json1 = ''' 22 | { 23 | "some" : { 24 | "nested" : { 25 | "json" : "with \\"val'ue", 26 | "anothervalue": 4, 27 | "withlist" : [ 28 | { "name" :"name1"} , {"name": "name2"} 29 | ] 30 | } 31 | } 32 | } 33 | ''' 34 | 35 | @Unroll 36 | def 'should convert a json with a map as root to a map of path to value '() { 37 | expect: 38 | verifiable.jsonPath() == expectedJsonPath 39 | and: 40 | JsonPath.parse(json1).read(expectedJsonPath, JSONArray) 41 | where: 42 | verifiable || expectedJsonPath 43 | assertThat(json1).field("some").field("nested").field("anothervalue").isEqualTo(4) || '''$.some.nested[?(@.anothervalue == 4)]''' 44 | assertThat(json1).field("some").field("nested").field("anothervalue") || '''$.some.nested.anothervalue''' 45 | assertThatJson(json1).field("some").field("nested").field("anothervalue").isEqualTo(4) || '''$.some.nested[?(@.anothervalue == 4)]''' 46 | assertThat(json1).field("some").field("nested").array("withlist").contains("name").isEqualTo("name1") || '''$.some.nested.withlist[*][?(@.name == 'name1')]''' 47 | assertThat(json1).field("some").field("nested").array("withlist").contains("name").isEqualTo("name2") || '''$.some.nested.withlist[*][?(@.name == 'name2')]''' 48 | assertThat(json1).field("some").field("nested").field("json").isEqualTo("with \"val'ue") || '''$.some.nested[?(@.json == 'with "val\\'ue')]''' 49 | assertThat(json1).field("some", "nested", "json").isEqualTo("with \"val'ue") || '''$.some.nested[?(@.json == 'with "val\\'ue')]''' 50 | } 51 | 52 | @Shared String json2 = '''{ 53 | "property1": "a", 54 | "property2": "b" 55 | }''' 56 | 57 | @Unroll 58 | def "should generate assertions for simple response body"() { 59 | expect: 60 | verifiable.jsonPath() == expectedJsonPath 61 | and: 62 | JsonPath.parse(json2).read(expectedJsonPath, JSONArray) 63 | where: 64 | verifiable || expectedJsonPath 65 | assertThat(json2).field("property1").isEqualTo("a") || '''$[?(@.property1 == 'a')]''' 66 | assertThat(json2).field("property2").isEqualTo("b") || '''$[?(@.property2 == 'b')]''' 67 | } 68 | 69 | @Shared String json3 = '''{ 70 | "property1": "true", 71 | "property2": null, 72 | "property3": false 73 | }''' 74 | 75 | @Unroll 76 | def "should generate assertions for null and boolean values"() { 77 | expect: 78 | verifiable.jsonPath() == expectedJsonPath 79 | and: 80 | JsonPath.parse(json3).read(expectedJsonPath, JSONArray) 81 | where: 82 | verifiable || expectedJsonPath 83 | assertThat(json3).field("property1").isEqualTo("true") || '''$[?(@.property1 == 'true')]''' 84 | assertThat(json3).field("property2").isNull() || '''$[?(@.property2 == null)]''' 85 | assertThat(json3).field("property3").isEqualTo(false) || '''$[?(@.property3 == false)]''' 86 | } 87 | 88 | @Shared Map json4 = [ 89 | property1: 'a', 90 | property2: [ 91 | [a: 'sth'], 92 | [b: 'sthElse'] 93 | ] 94 | ] 95 | 96 | @Unroll 97 | def "should generate assertions for simple response body constructed from map with a list"() { 98 | expect: 99 | verifiable.jsonPath() == expectedJsonPath 100 | and: 101 | JsonPath.parse(json4).read(expectedJsonPath, JSONArray) 102 | where: 103 | verifiable || expectedJsonPath 104 | assertThat(toJson(json4)).field("property1").isEqualTo("a") || '''$[?(@.property1 == 'a')]''' 105 | assertThat(toJson(json4)).array("property2").contains("a").isEqualTo("sth") || '''$.property2[*][?(@.a == 'sth')]''' 106 | assertThat(toJson(json4)).array("property2").contains("b").isEqualTo("sthElse") || '''$.property2[*][?(@.b == 'sthElse')]''' 107 | } 108 | 109 | @Shared Map json5 = [ 110 | property: [ 111 | 14: 0.0, 112 | 7 : 0.0 113 | ] 114 | ] 115 | 116 | @Unroll 117 | def "should generate assertions for a response body containing map with integers as keys"() { 118 | expect: 119 | verifiable.jsonPath() == expectedJsonPath 120 | and: 121 | JsonPath.parse(toJson(json5)).read(expectedJsonPath, JSONArray) 122 | where: 123 | verifiable || expectedJsonPath 124 | assertThat(toJson(json5)).field("property").field(7).isEqualTo(0.0) || '''$.property[?(@.7 == 0.0)]''' 125 | assertThat(toJson(json5)).field("property").field(14).isEqualTo(0.0) || '''$.property[?(@.14 == 0.0)]''' 126 | } 127 | 128 | @Shared String json6 = '''[ 129 | { 130 | "property1": "a" 131 | }, 132 | { 133 | "property2": "b" 134 | }]''' 135 | 136 | @Unroll 137 | def "should generate assertions for array in response body"() { 138 | expect: 139 | verifiable.jsonPath() == expectedJsonPath 140 | and: 141 | JsonPath.parse(json6).read(expectedJsonPath, JSONArray) 142 | where: 143 | verifiable || expectedJsonPath 144 | assertThat(json6).array().contains("property1").isEqualTo("a") || '''$[*][?(@.property1 == 'a')]''' 145 | assertThat(json6).array().contains("property2").isEqualTo("b") || '''$[*][?(@.property2 == 'b')]''' 146 | } 147 | 148 | @Shared String json7 = '''{ 149 | "property1": [ 150 | { "property2": "test1"}, 151 | { "property3": "test2"} 152 | ] 153 | }''' 154 | 155 | @Unroll 156 | def "should generate assertions for array inside response body element"() { 157 | expect: 158 | verifiable.jsonPath() == expectedJsonPath 159 | and: 160 | JsonPath.parse(json7).read(expectedJsonPath, JSONArray) 161 | where: 162 | verifiable || expectedJsonPath 163 | assertThat(json7).array("property1").contains("property2").isEqualTo("test1") || '''$.property1[*][?(@.property2 == 'test1')]''' 164 | assertThat(json7).array("property1").contains("property3").isEqualTo("test2") || '''$.property1[*][?(@.property3 == 'test2')]''' 165 | } 166 | 167 | @Shared String json8 = """{ 168 | "property1": "a", 169 | "property2": {"property3": "b"} 170 | }""" 171 | 172 | def "should generate assertions for nested objects in response body"() { 173 | expect: 174 | verifiable.jsonPath() == expectedJsonPath 175 | and: 176 | JsonPath.parse(json8).read(expectedJsonPath, JSONArray) 177 | where: 178 | verifiable || expectedJsonPath 179 | assertThat(json8).field("property2").field("property3").isEqualTo("b") || '''$.property2[?(@.property3 == 'b')]''' 180 | assertThat(json8).field("property1").isEqualTo("a") || '''$[?(@.property1 == 'a')]''' 181 | } 182 | 183 | @Shared Map json9 = [ 184 | property1: "a", 185 | property2: 123 186 | ] 187 | 188 | @Unroll 189 | def "should generate regex assertions for map objects in response body"() { 190 | expect: 191 | verifiable.jsonPath() == expectedJsonPath 192 | and: 193 | JsonPath.parse(json9).read(expectedJsonPath, JSONArray) 194 | where: 195 | verifiable || expectedJsonPath 196 | assertThat(toJson(json9)).field("property2").matches("[0-9]{3}") || '''$[?(@.property2 =~ /[0-9]{3}/)]''' 197 | assertThat(toJson(json9)).field("property1").isEqualTo("a") || '''$[?(@.property1 == 'a')]''' 198 | } 199 | 200 | def "should generate escaped regex assertions for string objects in response body"() { 201 | given: 202 | Map json = [ 203 | property2: 123123 204 | ] 205 | expect: 206 | def verifiable = assertThat(toJson(json)).field("property2").matches("\\d+") 207 | verifiable.jsonPath() == '''$[?(@.property2 =~ /\\d+/)]''' 208 | and: 209 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 210 | } 211 | 212 | @Shared Map json10 = [ 213 | errors: [ 214 | [property: "bank_account_number", 215 | message: "incorrect_format"] 216 | ] 217 | ] 218 | 219 | @Unroll 220 | def "should work with more complex stuff and jsonpaths"() { 221 | expect: 222 | verifiable.jsonPath() == expectedJsonPath 223 | and: 224 | JsonPath.parse(json10).read(expectedJsonPath, JSONArray) 225 | where: 226 | verifiable || expectedJsonPath 227 | assertThat(toJson(json10)).array("errors").contains("property").isEqualTo("bank_account_number") || '''$.errors[*][?(@.property == 'bank_account_number')]''' 228 | assertThat(toJson(json10)).array("errors").contains("message").isEqualTo("incorrect_format") || '''$.errors[*][?(@.message == 'incorrect_format')]''' 229 | } 230 | 231 | @Shared String json11 = ''' 232 | [{ 233 | "place": 234 | { 235 | "bounding_box": 236 | { 237 | "coordinates": 238 | [[ 239 | [-77.119759,38.995548], 240 | [-76.909393,38.791645] 241 | ]] 242 | } 243 | } 244 | }] 245 | ''' 246 | 247 | @Unroll 248 | def "should manage to parse a triple array"() { 249 | expect: 250 | verifiable.jsonPath() == expectedJsonPath 251 | and: 252 | JsonPath.parse(json11).read(expectedJsonPath, JSONArray) 253 | where: 254 | verifiable || expectedJsonPath 255 | assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(38.995548).value() || '''$[*].place.bounding_box.coordinates[*][*][?(@ == 38.995548)]''' 256 | assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(-77.119759).value() || '''$[*].place.bounding_box.coordinates[*][*][?(@ == -77.119759)]''' 257 | assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(-76.909393).value() || '''$[*].place.bounding_box.coordinates[*][*][?(@ == -76.909393)]''' 258 | assertThat(json11).array().field("place").field("bounding_box").array("coordinates").array().array().arrayField().contains(38.791645).value() || '''$[*].place.bounding_box.coordinates[*][*][?(@ == 38.791645)]''' 259 | 260 | } 261 | 262 | @Shared String json12 = ''' 263 | [{ 264 | "place": 265 | { 266 | "bounding_box": 267 | { 268 | "coordinates": 269 | [[[ 270 | [-77.119759,38.995548], 271 | [-76.909393,38.791645] 272 | ]]] 273 | } 274 | } 275 | }] 276 | ''' 277 | 278 | @Unroll 279 | def "should manage to parse a quadriple array"() { 280 | expect: 281 | verifiable.jsonPath() == expectedJsonPath 282 | and: 283 | JsonPath.parse(json12).read(expectedJsonPath, JSONArray) 284 | where: 285 | verifiable || expectedJsonPath 286 | assertThat(json12).array().field("place").field("bounding_box").array("coordinates").array().array().array().arrayField().contains(38.995548).value() || '''$[*].place.bounding_box.coordinates[*][*][*][?(@ == 38.995548)]''' 287 | assertThat(json12).array().field("place").field("bounding_box").array("coordinates").array().array().array().arrayField().contains(-77.119759).value() || '''$[*].place.bounding_box.coordinates[*][*][*][?(@ == -77.119759)]''' 288 | assertThat(json12).array().field("place").field("bounding_box").array("coordinates").array().array().array().arrayField().contains(-76.909393).value() || '''$[*].place.bounding_box.coordinates[*][*][*][?(@ == -76.909393)]''' 289 | assertThat(json12).array().field("place").field("bounding_box").array("coordinates").array().array().array().arrayField().contains(38.791645).value() || '''$[*].place.bounding_box.coordinates[*][*][*][?(@ == 38.791645)]''' 290 | 291 | } 292 | 293 | @Shared String jsonArrayInArray= ''' 294 | { 295 | "coordinates": 296 | [ 297 | [-77.119759,38.995548], 298 | [-76.909393,38.791645] 299 | ] 300 | } 301 | } 302 | ''' 303 | 304 | @Unroll 305 | def "should manage to parse array in array"() { 306 | expect: 307 | verifiable.jsonPath() == expectedJsonPath 308 | and: 309 | JsonPath.parse(jsonArrayInArray).read(expectedJsonPath, JSONArray) 310 | where: 311 | verifiable || expectedJsonPath 312 | assertThat(jsonArrayInArray).array("coordinates").array().arrayField().contains(38.995548).value() || '''$.coordinates[*][?(@ == 38.995548)]''' 313 | assertThat(jsonArrayInArray).array("coordinates").array().arrayField().contains(-77.119759).value() || '''$.coordinates[*][?(@ == -77.119759)]''' 314 | assertThat(jsonArrayInArray).array("coordinates").array().arrayField().contains(-76.909393).value() || '''$.coordinates[*][?(@ == -76.909393)]''' 315 | assertThat(jsonArrayInArray).array("coordinates").array().arrayField().contains(38.791645).value() || '''$.coordinates[*][?(@ == 38.791645)]''' 316 | } 317 | 318 | @Unroll 319 | def 'should convert a json with list as root to a map of path to value'() { 320 | expect: 321 | assertThat(json).array().field("some").field("nested").field("json").isEqualTo("with value").jsonPath() == '''$[*].some.nested[?(@.json == 'with value')]''' 322 | assertThat(json).array().field("some").field("nested").field("anothervalue").isEqualTo(4).jsonPath() == '''$[*].some.nested[?(@.anothervalue == 4)]''' 323 | assertThat(json).array().field("some").field("nested").array("withlist").contains("name").isEqualTo("name1").jsonPath() == '''$[*].some.nested.withlist[*][?(@.name == 'name1')]''' 324 | assertThat(json).array().field("some").field("nested").array("withlist").contains("name").isEqualTo("name2").jsonPath() == '''$[*].some.nested.withlist[*][?(@.name == 'name2')]''' 325 | assertThat(json).array().field("some").field("nested").array("withlist").field("anothernested").field("name").isEqualTo("name3").jsonPath() == '''$[*].some.nested.withlist[*].anothernested[?(@.name == 'name3')]''' 326 | where: 327 | json << [ 328 | ''' 329 | [ { 330 | "some" : { 331 | "nested" : { 332 | "json" : "with value", 333 | "anothervalue": 4, 334 | "withlist" : [ 335 | { "name" :"name1"} , {"name": "name2"}, {"anothernested": { "name": "name3"} } 336 | ] 337 | } 338 | } 339 | }, 340 | { 341 | "someother" : { 342 | "nested" : { 343 | "json" : "with value", 344 | "anothervalue": 4, 345 | "withlist" : [ 346 | { "name" :"name1"} , {"name": "name2"} 347 | ] 348 | } 349 | } 350 | } 351 | ] 352 | ''', 353 | ''' 354 | [{ 355 | "someother" : { 356 | "nested" : { 357 | "json" : "with value", 358 | "anothervalue": 4, 359 | "withlist" : [ 360 | { "name" :"name1"} , {"name": "name2"} 361 | ] 362 | } 363 | } 364 | }, 365 | { 366 | "some" : { 367 | "nested" : { 368 | "json" : "with value", 369 | "anothervalue": 4, 370 | "withlist" : [ 371 | {"name": "name2"}, {"anothernested": { "name": "name3"} }, { "name" :"name1"} 372 | ] 373 | } 374 | } 375 | } 376 | ]'''] 377 | } 378 | 379 | def "should run json path when provided manually"() { 380 | given: 381 | String json = """{ 382 | "property1": "a", 383 | "property2": {"property3": "b"} 384 | }""" 385 | and: 386 | String jsonPath = '''$[?(@.property1 == 'a')]''' 387 | expect: 388 | assertThat(json).matchesJsonPath(jsonPath) 389 | and: 390 | JsonPath.parse(json).read(jsonPath, JSONArray) 391 | } 392 | 393 | def "should throw exception when json path is not matched"() { 394 | given: 395 | String json = """{ 396 | "property1": "a", 397 | "property2": {"property3": "b"} 398 | }""" 399 | and: 400 | String jsonPath = '''$[?(@.property1 == 'c')]''' 401 | when: 402 | assertThat(json).matchesJsonPath(jsonPath) 403 | then: 404 | IllegalStateException illegalStateException = thrown(IllegalStateException) 405 | illegalStateException.message.contains("Parsed JSON") 406 | illegalStateException.message.contains("doesn't match") 407 | } 408 | 409 | def "should not throw exception when json path is not matched and system prop overrides the check"() { 410 | given: 411 | String json = """{ 412 | "property1": "a", 413 | "property2": {"property3": "b"} 414 | }""" 415 | and: 416 | String jsonPath = '''$[?(@.property1 == 'c')]''' 417 | when: 418 | assertThat(json).withoutThrowingException().matchesJsonPath(jsonPath) 419 | then: 420 | noExceptionThrown() 421 | } 422 | 423 | def "should generate escaped regex assertions for boolean objects in response body"() { 424 | given: 425 | Map json = [ 426 | property2: true 427 | ] 428 | expect: 429 | def verifiable = assertThat(toJson(json)).field("property2").matches('true|false') 430 | verifiable.jsonPath() == '''$[?(@.property2 =~ /true|false/)]''' 431 | and: 432 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 433 | } 434 | 435 | def "should generate escaped regex assertions for numbers objects in response body"() { 436 | given: 437 | Map json = [ 438 | property2: 50 439 | ] 440 | expect: 441 | def verifiable = assertThat(toJson(json)).field("property2").matches('[0-9]{2}') 442 | verifiable.jsonPath() == '''$[?(@.property2 =~ /[0-9]{2}/)]''' 443 | and: 444 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 445 | } 446 | 447 | def "should escape regular expression properly"() { 448 | given: 449 | String json = """ 450 | { 451 | "path" : "/api/12", 452 | "correlationId" : 123456 453 | } 454 | """ 455 | expect: 456 | DocumentContext parsedJson = JsonPath.parse(json) 457 | def verifiable = assertThatJson(parsedJson).field("path").matches("^/api/[0-9]{2}\$") 458 | verifiable.jsonPath() == '''$[?(@.path =~ /^\\/api\\/[0-9]{2}$/)]''' 459 | and: 460 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 461 | } 462 | 463 | @Issue("Accurest#193") 464 | def "should escape single quotes in a quoted string"() { 465 | given: 466 | String json = """ 467 | { 468 | "text" : "text with 'quotes' inside" 469 | } 470 | """ 471 | expect: 472 | DocumentContext parsedJson = JsonPath.parse(json) 473 | def verifiable = assertThatJson(parsedJson).field("text").isEqualTo("text with 'quotes' inside") 474 | verifiable.jsonPath() == '''$[?(@.text == 'text with \\'quotes\\' inside')]''' 475 | and: 476 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 477 | } 478 | 479 | @Issue("Accurest#193") 480 | def "should escape double quotes in a quoted string"() { 481 | given: 482 | String json = """ 483 | { 484 | "text" : "text with \\"quotes\\" inside" 485 | } 486 | """ 487 | expect: 488 | DocumentContext parsedJson = JsonPath.parse(json) 489 | def verifiable = assertThatJson(parsedJson).field("text").isEqualTo('''text with "quotes" inside''') 490 | verifiable.jsonPath() == '''$[?(@.text == 'text with "quotes" inside')]''' 491 | and: 492 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 493 | } 494 | 495 | def 'should resolve the value of JSON via JSON Path'() { 496 | given: 497 | String json = 498 | ''' 499 | [ { 500 | "some" : { 501 | "nested" : { 502 | "json" : "with value", 503 | "anothervalue": 4, 504 | "withlist" : [ 505 | { "name" :"name1"} , 506 | {"name": "name2"}, 507 | {"anothernested": { "name": "name3"} } 508 | ] 509 | } 510 | } 511 | }, 512 | { 513 | "someother" : { 514 | "nested" : { 515 | "json" : true, 516 | "anothervalue": 4, 517 | "withlist" : [ 518 | { "name" :"name1"} , {"name": "name2"} 519 | ], 520 | "withlist2" : [ 521 | "a", "b" 522 | ] 523 | } 524 | } 525 | } 526 | ] 527 | ''' 528 | expect: 529 | com.toomuchcoding.jsonassert.JsonPath.builder(json).array().field("some").field("nested").field("json").read(String) == 'with value' 530 | com.toomuchcoding.jsonassert.JsonPath.builder(json).array().field("some").field("nested").field("anothervalue").read(Integer) == 4 531 | assertThat(json).array().field("some").field("nested").array("withlist").field("name").read(List) == ['name1', 'name2'] 532 | assertThat(json).array().field("someother").field("nested").array("withlist2").read(List) == ['a', 'b'] 533 | assertThat(json).array().field("someother").field("nested").field("json").read(Boolean) == true 534 | } 535 | 536 | def 'should assert json with only top list elements'() { 537 | given: 538 | String json = '''["Java", "Java8", "Spring", "SpringBoot", "Stream"]''' 539 | expect: 540 | assertThatJson(json).arrayField().contains("Java8").value() 541 | assertThatJson(json).arrayField().contains("Spring").value() 542 | assertThatJson(json).arrayField().contains("Java").value() 543 | assertThatJson(json).arrayField().contains("Stream").value() 544 | assertThatJson(json).arrayField().contains("SpringBoot").value() 545 | } 546 | 547 | @Issue("#9") 548 | def 'should match array containing an array of primitives'() { 549 | given: 550 | String json = '''{"first_name":"existing", 551 | "partners":[ 552 | { "role":"AGENT", 553 | "payment_methods":[ "BANK", "CASH" ] 554 | } 555 | ] 556 | }''' 557 | expect: 558 | def verifiable = assertThatJson(json).array("partners").array("payment_methods").arrayField().isEqualTo("BANK").value() 559 | verifiable.jsonPath() == '''$.partners[*].payment_methods[?(@ == 'BANK')]''' 560 | and: 561 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 562 | } 563 | 564 | @Issue("#9") 565 | def 'should match pattern in array'() { 566 | given: 567 | String json = '''{ "authorities": ["ROLE_ADMIN"] }''' 568 | expect: 569 | def verifiable = assertThatJson(json).array("authorities").arrayField().matches("^[a-zA-Z0-9_\\- ]+\$").value() 570 | verifiable.jsonPath() == '''$.authorities[?(@ =~ /^[a-zA-Z0-9_\\- ]+$/)]''' 571 | and: 572 | JsonPath.parse(json).read(verifiable.jsonPath(), JSONArray) 573 | } 574 | 575 | @Issue("#10") 576 | def 'should manage to parse array with string values'() { 577 | given: 578 | String json = '''{ "some_list" : ["name1", "name2"] }''' 579 | expect: 580 | def v1 = assertThat(JsonPath.parse(json)).array("some_list").arrayField().isEqualTo("name1") 581 | def v2 = assertThat(JsonPath.parse(json)).array("some_list").arrayField().isEqualTo("name2") 582 | and: 583 | v1.jsonPath() == '''$.some_list[?(@ == 'name1')]''' 584 | v2.jsonPath() == '''$.some_list[?(@ == 'name2')]''' 585 | and: 586 | JsonPath.parse(json).read(v1.jsonPath(), JSONArray) 587 | JsonPath.parse(json).read(v2.jsonPath(), JSONArray) 588 | } 589 | 590 | def 'should parse an array of arrays that are root elements'() { 591 | given: 592 | String json = '''[["Programming", "Java"], ["Programming", "Java", "Spring", "Boot"]]''' 593 | expect: 594 | def v1 = assertThatJson(JsonPath.parse(json)).array().arrayField().isEqualTo("Java").value() 595 | and: 596 | v1.jsonPath() == '''$[*][?(@ == 'Java')]''' 597 | and: 598 | JsonPath.parse(json).read(v1.jsonPath(), JSONArray) 599 | } 600 | 601 | @Issue('#11') 602 | def 'should allow to check array size'() { 603 | given: 604 | String json = '''{ "some_list" : ["name1", "name2"] }''' 605 | expect: 606 | assertThat(json).array("some_list").hasSize(2) 607 | when: 608 | assertThat(json).array("some_list").hasSize(5) 609 | then: 610 | def ex = thrown(RuntimeException) 611 | ex instanceof IllegalStateException 612 | ex.message == '''Parsed JSON <{"some_list":["name1","name2"]}> doesn't have the size <5> for JSON path <$.some_list[*]>. The size is <2>''' 613 | } 614 | 615 | @Issue('#11') 616 | def 'should allow to check size of root array'() { 617 | given: 618 | String json = '''["name1", "name2"]''' 619 | expect: 620 | assertThat(json).hasSize(2) 621 | when: 622 | assertThat(json).hasSize(5) 623 | then: 624 | def ex = thrown(RuntimeException) 625 | ex instanceof IllegalStateException 626 | ex.message == '''Parsed JSON <["name1","name2"]> doesn't have the size <5> for JSON path <$>. The size is <2>''' 627 | } 628 | 629 | @Issue('#11') 630 | def 'should allow to check size of a nested array'() { 631 | given: 632 | String json = 633 | ''' 634 | [ { 635 | "some" : { 636 | "nested" : { 637 | "json" : "with value", 638 | "anothervalue": 4, 639 | "withlist" : [ 640 | { "name" :"name1"} , 641 | {"name": "name2"}, 642 | {"anothernested": { "name": "name3"} } 643 | ] 644 | } 645 | } 646 | }, 647 | { 648 | "someother" : { 649 | "nested" : { 650 | "json" : true, 651 | "anothervalue": 4, 652 | "withlist" : [ 653 | { "name" :"name1"} , {"name": "name2"} 654 | ], 655 | "withlist2" : [ 656 | "a", "b" 657 | ] 658 | } 659 | } 660 | } 661 | ]''' 662 | expect: 663 | assertThat(json).array().field("someother").field("nested").array("withlist2").hasSize(2) 664 | when: 665 | assertThat(json).array().field("someother").field("nested").array("withlist2").hasSize(5) 666 | then: 667 | def ex = thrown(RuntimeException) 668 | ex instanceof IllegalStateException 669 | ex.message == '''Parsed JSON <[{"some":{"nested":{"json":"with value","anothervalue":4,"withlist":[{"name":"name1"},{"name":"name2"},{"anothernested":{"name":"name3"}}]}}},{"someother":{"nested":{"json":true,"anothervalue":4,"withlist":[{"name":"name1"},{"name":"name2"}],"withlist2":["a","b"]}}}]> doesn't have the size <5> for JSON path <$[*].someother.nested.withlist2[*]>. The size is <2>''' 670 | } 671 | 672 | @Issue('#11') 673 | def 'should allow to check size of a named array'() { 674 | given: 675 | def json = [ 676 | property1: 'a', 677 | property2: [ 678 | [a: 'sth'], 679 | [b: 'sthElse'] 680 | ] 681 | ] 682 | expect: 683 | assertThat(toJson(json)).array("property2").hasSize(2) 684 | when: 685 | assertThat(toJson(json)).array("property2").hasSize(5) 686 | then: 687 | def ex = thrown(RuntimeException) 688 | ex instanceof IllegalStateException 689 | ex.message == '''Parsed JSON <{"property1":"a","property2":[{"a":"sth"},{"b":"sthElse"}]}> doesn't have the size <5> for JSON path <$.property2[*]>. The size is <2>''' 690 | } 691 | 692 | @Issue('#11') 693 | def 'should allow to check size of two nameless arrays'() { 694 | given: 695 | String json = '''[["Programming", "Java"], ["Programming", "Java", "Spring", "Boot"], ["Programming", "Java", "Spring", "Boot", "Master"]]''' 696 | expect: 697 | assertThat(json).hasSize(3) 698 | assertThat(json).elementWithIndex(0).hasSize(2) 699 | assertThat(json).elementWithIndex(1).hasSize(4) 700 | when: 701 | assertThat(json).array().hasSize(4) 702 | then: 703 | def ex = thrown(RuntimeException) 704 | ex instanceof IllegalStateException 705 | ex.message == '''Parsed JSON <[["Programming","Java"],["Programming","Java","Spring","Boot"],["Programming","Java","Spring","Boot","Master"]]> doesn't have the size <4> for JSON path <$[*]>. The size is <3>''' 706 | } 707 | 708 | @Issue('#11') 709 | def 'should allow to check size of two nameless arrays in a nameless array'() { 710 | given: 711 | String json = '''[[["Programming", "Java"], ["Programming", "Java", "Spring", "Boot"]]]''' 712 | expect: 713 | assertThat(json).hasSize(1) 714 | assertThat(json).elementWithIndex(0).elementWithIndex(0).hasSize(2) 715 | assertThat(json).elementWithIndex(0).elementWithIndex(1).hasSize(4) 716 | when: 717 | assertThat(json).elementWithIndex(0).elementWithIndex(1).hasSize(5) 718 | then: 719 | def ex = thrown(RuntimeException) 720 | ex instanceof IllegalStateException 721 | ex.message == '''Parsed JSON <[[["Programming","Java"],["Programming","Java","Spring","Boot"]]]> doesn't have the size <5> for JSON path <$[0][1]>. The size is <4>''' 722 | } 723 | 724 | @Issue('#11') 725 | def 'should allow to check array size of nameless array'() { 726 | given: 727 | String json = '''{ "coordinates" : [[ 728 | ["name1", "name2"], 729 | ["name3", "name4"] 730 | ]] }''' 731 | expect: 732 | assertThat(json).array("coordinates").array().hasSize(2) 733 | when: 734 | assertThat(json).array("coordinates").array().hasSize(5) 735 | then: 736 | def ex = thrown(RuntimeException) 737 | ex instanceof IllegalStateException 738 | ex.message == '''Parsed JSON <{"coordinates":[[["name1","name2"],["name3","name4"]]]}> doesn't have the size <5> for JSON path <$.coordinates[*][*]>. The size is <2>''' 739 | } 740 | 741 | @Unroll 742 | def 'should fail on non existent path'() { 743 | when: 744 | assertThat(json instanceof Map ? toJson(json) : json).field("non").field("existant").field("field").isEqualTo("imaginary value") 745 | then: 746 | def ex = thrown(RuntimeException) 747 | ex.cause instanceof PathNotFoundException 748 | where: 749 | json << [ json1, json2, json3, json4, json5, json6, json7, json8, json9, json10, json11 ] 750 | } 751 | 752 | @Issue('#14') 753 | def 'should allow to check if size is empty'() { 754 | given: 755 | String json = '''{ "coordinates" : [], "foo": ["bar", "baz"] }''' 756 | expect: 757 | assertThat(json).array("coordinates").isEmpty() 758 | when: 759 | assertThat(json).array("foo").isEmpty() 760 | then: 761 | def ex = thrown(RuntimeException) 762 | ex instanceof IllegalStateException 763 | ex.message == '''Parsed JSON [{"coordinates":[],"foo":["bar","baz"]}] with the JSON path [$.foo[*]] is not empty!''' 764 | } 765 | 766 | @Issue('#17') 767 | def 'should ignore exception on empty check'() { 768 | given: 769 | String json = '''{ "coordinates" : [], "foo": ["bar", "baz"] }''' 770 | when: 771 | assertThat(json).withoutThrowingException().array("foo").isEmpty() 772 | then: 773 | noExceptionThrown() 774 | } 775 | 776 | @Issue('#16') 777 | def 'should throw exception when an empty array is returned'() { 778 | given: 779 | String json = '''{}''' 780 | when: 781 | assertThatJson(json).field("doesNotExist").matches("[\\p{L}]*") 782 | then: 783 | def ex = thrown(RuntimeException) 784 | ex instanceof IllegalStateException 785 | ex.message == '''Parsed JSON [{}] doesn't match the JSON path [$[?(@.doesNotExist =~ /[\\p{L}]*/)]]''' 786 | when: 787 | assertThatJson(json).array("c").matches("[\\p{L}]*") 788 | then: 789 | ex = thrown(RuntimeException) 790 | ex instanceof IllegalStateException 791 | ex.message == '''Parsed JSON [{}] doesn't match the JSON path [$[?(@.c =~ /[\\p{L}]*/)]]''' 792 | } 793 | 794 | @Issue('#18') 795 | def 'should read types of objects'() { 796 | given: 797 | String json = '''{ "foo": 46 }''' 798 | when: 799 | assertThatJson(json).field("foo").isInstanceOf(Number) 800 | then: 801 | noExceptionThrown() 802 | when: 803 | assertThatJson(json).field("foo").isInstanceOf(String) 804 | then: 805 | RuntimeException ex = thrown(RuntimeException) 806 | ex instanceof IllegalStateException 807 | ex.message == '''For JSON path [$.foo] instance of [Integer] is not assignable from [String]''' 808 | } 809 | 810 | @Issue('#18') 811 | def 'should read big numbers'() { 812 | given: 813 | String json = '''{ "largeNum": 55534673.56, "bigInt": 2147483647, "decimals": 0.1287361923123}''' 814 | when: 815 | assertThatJson(json).field("largeNum").isEqualTo(55534673.56 as Double) 816 | then: 817 | noExceptionThrown() 818 | when: 819 | assertThatJson(json).field("bigInt").isEqualTo(Integer.MAX_VALUE as Integer) 820 | then: 821 | noExceptionThrown() 822 | when: 823 | assertThatJson(json).field("decimals").isEqualTo(0.1287361923123 as Double) 824 | then: 825 | noExceptionThrown() 826 | } 827 | 828 | @Issue('#28') 829 | def 'should match {} as empty'() { 830 | given: 831 | String json = '''{ "field1": {}, field2: [] }}''' 832 | when: 833 | assertThatJson(json).field("field1").isEmpty() 834 | then: 835 | noExceptionThrown() 836 | when: 837 | assertThatJson(json).field("field2").isEmpty() 838 | then: 839 | noExceptionThrown() 840 | } 841 | 842 | @Issue('#31') 843 | def 'should work with scientific notation'() { 844 | given: 845 | String json = '''{ "n": 1.12E-12}''' 846 | when: 847 | assertThatJson(json).field("n").isEqualTo(1.12E-12) 848 | then: 849 | noExceptionThrown() 850 | } 851 | 852 | } 853 | -------------------------------------------------------------------------------- /tests/src/test/groovy/shaded/com/toomuchcoding/jsonassert/JsonPathSpec.groovy: -------------------------------------------------------------------------------- 1 | package shaded.com.toomuchcoding.jsonassert 2 | 3 | import com.toomuchcoding.jsonassert.JsonPath 4 | import spock.lang.Specification 5 | 6 | /** 7 | * @author Marcin Grzejszczak 8 | */ 9 | class JsonPathSpec extends Specification { 10 | 11 | def "should generate proper JSON paths"() { 12 | expect: 13 | jsonPath == expectedJsonPath 14 | where: 15 | jsonPath || expectedJsonPath 16 | JsonPath.builder().field("some").field("nested").field("anothervalue").isEqualTo(4).jsonPath() || '''$.some.nested[?(@.anothervalue == 4)]''' 17 | JsonPath.builder().field("some").field("nested").field("anothervalue").isEqualTo(4).jsonPath() || '''$.some.nested[?(@.anothervalue == 4)]''' 18 | JsonPath.builder().field("some").field("nested").array("withlist").contains("name").isEqualTo("name1").jsonPath() || '''$.some.nested.withlist[*][?(@.name == 'name1')]''' 19 | JsonPath.builder().field("some").field("nested").array("withlist").contains("name").isEqualTo("name2").jsonPath() || '''$.some.nested.withlist[*][?(@.name == 'name2')]''' 20 | JsonPath.builder().field("some").field("nested").field("json").isEqualTo("with \"val'ue").jsonPath() || '''$.some.nested[?(@.json == 'with "val\\'ue')]''' 21 | JsonPath.builder().field("some", "nested", "json").isEqualTo("with \"val'ue").jsonPath() || '''$.some.nested[?(@.json == 'with "val\\'ue')]''' 22 | } 23 | 24 | } 25 | --------------------------------------------------------------------------------