├── .github ├── ISSUE_TEMPLATE │ ├── custom.md │ ├── bug_report.md │ └── feature_request.md ├── renovate.json └── workflows │ ├── maven.yml │ └── pages.yml ├── src ├── main │ └── java │ │ └── io │ │ └── json │ │ └── compare │ │ ├── CompareMode.java │ │ ├── JsonComparator.java │ │ ├── util │ │ ├── MessageUtil.java │ │ └── JsonUtils.java │ │ ├── DefaultJsonComparator.java │ │ ├── matcher │ │ ├── JsonMatcher.java │ │ ├── JsonValueMatcher.java │ │ ├── JsonPathMatcher.java │ │ ├── JsonArrayMatcher.java │ │ ├── AbstractJsonMatcher.java │ │ └── JsonObjectMatcher.java │ │ └── JSONCompare.java └── test │ ├── java │ └── io │ │ └── json │ │ └── compare │ │ ├── matcher │ │ ├── issues │ │ │ ├── Issue2Test.java │ │ │ ├── Issue9Test.java │ │ │ ├── Issue13Test.java │ │ │ ├── Issue12Test.java │ │ │ ├── Issue10Test.java │ │ │ ├── Issue6Test.java │ │ │ ├── Issue8Test.java │ │ │ ├── Issue1Test.java │ │ │ ├── Issue5Test.java │ │ │ ├── Issue7Test.java │ │ │ └── Issue11Test.java │ │ ├── JSONEmptyValuesCompareTests.java │ │ ├── JSONofDifferentTypesCompareTests.java │ │ ├── JSONBooleanCompareTests.java │ │ ├── diffs │ │ │ ├── JsonRealWorldDiffTests.java │ │ │ ├── JsonValueDiffTests.java │ │ │ ├── JsonPathDiffTests.java │ │ │ ├── JsonDiffsAsListTests.java │ │ │ ├── JsonCustomComparatorDiffTests.java │ │ │ └── JsonArrayDiffTests.java │ │ ├── JSONValueNodeCompareTests.java │ │ ├── JSONNullCompareTests.java │ │ ├── JSONConvertibleObjectsCompareTests.java │ │ ├── JSONArrayCompareTests.java │ │ ├── JSONCompareMessageTests.java │ │ ├── JSONObjectCompareTests.java │ │ ├── JSONMixedCompareTests.java │ │ ├── JSONRegexCompareTests.java │ │ ├── JSONUseCaseCompareTests.java │ │ └── readme │ │ │ └── ReadmeTests.java │ │ └── util │ │ ├── JsonPrettyPrintlTest.java │ │ ├── MessageUtilTest.java │ │ └── JsonUtilsTest.java │ └── resources │ └── bigJsons │ ├── expectedLargeJson.json │ └── expectedWrongLargeJson.json ├── _config.yml ├── .gitignore ├── LICENSE ├── Changelog.md └── pom.xml /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/CompareMode.java: -------------------------------------------------------------------------------- 1 | package io.json.compare; 2 | 3 | public enum CompareMode { 4 | JSON_OBJECT_NON_EXTENSIBLE, 5 | JSON_ARRAY_NON_EXTENSIBLE, 6 | JSON_ARRAY_STRICT_ORDER, 7 | REGEX_DISABLED 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/JsonComparator.java: -------------------------------------------------------------------------------- 1 | package io.json.compare; 2 | 3 | public interface JsonComparator { 4 | 5 | boolean compareValues(Object expected, Object actual); 6 | 7 | boolean compareFields(String expected, String actual); 8 | } 9 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "timezone": "Europe/Bucharest", 6 | "packageRules": [ 7 | { 8 | "matchUpdateTypes": [ 9 | "major", 10 | "minor", 11 | "patch" 12 | ], 13 | "matchCurrentVersion": "!/^0/", 14 | "automerge": true, 15 | "schedule": [ 16 | "after 1pm every weekday" 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: pages-themes/slate@v0.2.0 2 | plugins: 3 | - jekyll-seo-tag 4 | - jekyll-sitemap 5 | - jekyll-remote-theme 6 | 7 | title: "JSON Compare" 8 | tagline: "Java library for comparing Jsons" 9 | description: "Match any JSON convertible Java objects and check the differences between them when assertion fails" 10 | author: "Florin Slevoaca" 11 | url: "https://fslev.github.io/json-compare" 12 | permalink: pretty 13 | google_analytics: G-T93JGK4KGJ 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue2Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | /** 7 | * https://github.com/fslev/json-compare/issues/2 8 | */ 9 | public class Issue2Test { 10 | 11 | @Test 12 | public void testIssue() { 13 | String expected = 14 | "{\"field\":[\"value wi(th parentheses\", 4, 3]}"; 15 | String actual = 16 | "{\"field\":[3, 4, \"value wi(th parentheses\"]}"; 17 | JSONCompare.assertMatches(expected, actual); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue9Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class Issue9Test { 7 | 8 | @Test 9 | public void testJsonsDoNotMatchNegativelook() { 10 | String expected = "{\"records\":[ {\"a\":\".((?!0).)*\"} ]}"; 11 | String actual = "{\"records\":[ {\"b\":\"2\"}, {\"a\":\"s1s\"} ]}"; 12 | JSONCompare.assertMatches(expected, actual); 13 | } 14 | 15 | @Test 16 | public void testJsonsDoNotMatchNegativelook_negative() { 17 | String expected = "{\"records\":[ {\"a\":\".((?!0).)*\"} ]}"; 18 | String actual = "{\"records\":[ {\"b\":\"2\"}, {\"a\":\"s0s\"} ]}"; 19 | JSONCompare.assertNotMatches(expected, actual); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/util/JsonPrettyPrintlTest.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.util; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.node.TextNode; 5 | import io.json.compare.JSONCompare; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | 11 | public class JsonPrettyPrintlTest { 12 | 13 | @Test 14 | public void testPrettyPrint() { 15 | String expected = "{\n" + 16 | " \"a\" : \"test\"\n" + 17 | "}"; 18 | assertEquals(expected, JSONCompare.prettyPrint(new ObjectMapper().createObjectNode().set("a", new TextNode("test")))); 19 | } 20 | 21 | @Test 22 | public void testNullPrettyPrint() { 23 | assertEquals("null", JSONCompare.prettyPrint(null)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Project specific excludes 3 | # 4 | 5 | tomcat 6 | 7 | # 8 | # Default excludes 9 | # 10 | 11 | # Binaries 12 | *.7z 13 | *.dmg 14 | *.gz 15 | *.iso 16 | *.jar 17 | *.rar 18 | *.tar 19 | *.zip 20 | *.war 21 | *.ear 22 | *.sar 23 | *.class 24 | .attach_* 25 | 26 | # Maven 27 | target/ 28 | 29 | # Gradle Files # 30 | # ################ 31 | .gradle 32 | .m2 33 | 34 | # # Build output directies 35 | /target 36 | */target 37 | /build 38 | */build 39 | 40 | # IntelliJ project files 41 | *.iml 42 | *.iws 43 | *.ipr 44 | .idea/ 45 | /bin 46 | 47 | # eclipse project file 48 | .settings/ 49 | .classpath 50 | .project 51 | 52 | # NetBeans specific 53 | nbproject/private/ 54 | build/ 55 | nbbuild/ 56 | dist/ 57 | nbdist/ 58 | nbactions.xml 59 | nb-configuration.xml 60 | 61 | 62 | # OS 63 | .DS_Store 64 | 65 | # Misc 66 | *.swp 67 | release.properties 68 | pom.xml.releaseBackup 69 | pom.xml.tag 70 | *.log -------------------------------------------------------------------------------- /src/test/resources/bigJsons/expectedLargeJson.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 200, 3 | "body": { 4 | "events": [ 5 | { 6 | "payload": { 7 | "type": "TEMPLATE_DONE", 8 | "context": { 9 | "name": "bigtest.com", 10 | "data": { 11 | "provid": "orgs.com", 12 | "srvid": "testWsProxyAndPermanentRedirect", 13 | "params": { 14 | "redit_tar": "http://permanent-target-158202273047525371.org", 15 | "proxy_subname": "somesub.com", 16 | "proxy_ip": ".*", 17 | "redirect_ipv4": ".*", 18 | "proof_own_token": ".*" 19 | }, 20 | "sub": "h1-158202273047258785", 21 | "long": "h1-158202273047258785.bigtest.com", 22 | "tenid": "org" 23 | } 24 | } 25 | }, 26 | "props": {} 27 | } 28 | ] 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/resources/bigJsons/expectedWrongLargeJson.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 200, 3 | "body": { 4 | "events": [ 5 | { 6 | "payload": { 7 | "type": "TEMPLATE_DONE", 8 | "context": { 9 | "name": "bigtest.com", 10 | "data": { 11 | "provid": "orgs.com", 12 | "srvid": "testWsProxyAndPermanentRedirect", 13 | "params": { 14 | "redir_tar": "http://permanent-target-158202273047525371.org", 15 | "proxy_subname": "somesub.com", 16 | "proxy_ip": ".*", 17 | "redirect_ipv4": ".*", 18 | "proof_own_token": ".*" 19 | }, 20 | "sub": "h1-158202273047258785a", 21 | "long": "h1-158202273047258785.bigtest.com", 22 | "tenid": "org" 23 | } 24 | } 25 | }, 26 | "props": {} 27 | } 28 | ] 29 | } 30 | } -------------------------------------------------------------------------------- /.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 | paths-ignore: 10 | - '**.md' 11 | pull_request: 12 | branches: [ main ] 13 | paths-ignore: 14 | - '**.md' 15 | workflow_dispatch: 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v6 24 | - name: Set up JDK 17 25 | uses: actions/setup-java@v5 26 | with: 27 | java-version: 17 28 | distribution: 'adopt' 29 | - name: Build with Maven 30 | env: 31 | COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} 32 | run: mvn clean test -B -Pcoveralls jacoco:report coveralls:report -Djacoco=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 fslev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/util/MessageUtil.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.util; 2 | 3 | public final class MessageUtil { 4 | 5 | private MessageUtil() { 6 | 7 | } 8 | 9 | private static final int AFTER_CROP_SIZE = 2048; 10 | private static final int S = 4096; 11 | private static final int M = 8192; 12 | private static final int L = 65535; 13 | 14 | public static String cropS(String msg) { 15 | return crop(msg, S); 16 | } 17 | 18 | public static String cropM(String msg) { 19 | return crop(msg, M); 20 | } 21 | 22 | public static String cropL(String msg) { 23 | return crop(msg, L); 24 | } 25 | 26 | private static String crop(String msg, int limit) { 27 | if (msg != null && msg.length() > limit) { 28 | String start = msg.substring(0, AFTER_CROP_SIZE / 2) + System.lineSeparator() + System.lineSeparator() + 29 | "<...cropped content...>" + System.lineSeparator() + System.lineSeparator(); 30 | String end = msg.substring(msg.length() - AFTER_CROP_SIZE / 2); 31 | return start + end; 32 | } 33 | return msg; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONEmptyValuesCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class JSONEmptyValuesCompareTests { 7 | 8 | @Test 9 | public void compareWithEmptyValue() { 10 | String expected = "{\"a\":\"\"}"; 11 | String actual = "{\"a\":\"\"}"; 12 | JSONCompare.assertMatches(expected, actual); 13 | } 14 | 15 | @Test 16 | public void compareWithEmptyValue_negative() { 17 | String expected = "{\"a\":\"not empty\"}"; 18 | String actual = "{\"a\":\"\"}"; 19 | JSONCompare.assertNotMatches(expected, actual); 20 | } 21 | 22 | @Test 23 | public void compareEmptyWithEmptyValue_negative() { 24 | String expected = "{\"a\":\"!\"}"; 25 | String actual = "{\"a\":\"\"}"; 26 | JSONCompare.assertNotMatches(expected, actual); 27 | } 28 | 29 | @Test 30 | public void compareEmptyAndNullValue_negative() { 31 | String expected = "{\"a\":\"\"}"; 32 | String actual = "{\"a\":null}"; 33 | JSONCompare.assertNotMatches(expected, actual); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONofDifferentTypesCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | public class JSONofDifferentTypesCompareTests { 10 | 11 | @Test 12 | public void compareObjectWithArray() { 13 | String expected = "{\"a\":\"!test\"}"; 14 | String actual = "[\"a\",\"b\"]"; 15 | JSONCompare.assertNotMatches(expected, actual); 16 | } 17 | 18 | @Test 19 | public void compareArrayWithObject() { 20 | String expected = "[\"a\",\"b\"]"; 21 | String actual = "{\"a\":\"!test\"}"; 22 | JSONCompare.assertNotMatches(expected, actual); 23 | } 24 | 25 | @Test 26 | public void compareStrangeValues() { 27 | String expected = "\"1\"Fds\"\""; 28 | String actual = "1"; 29 | RuntimeException exception = assertThrows(RuntimeException.class, () -> JSONCompare.assertNotMatches(expected, actual)); 30 | assertTrue(exception.getMessage().contains("Invalid JSON")); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Build job 25 | build: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v6 30 | - name: Setup Pages 31 | uses: actions/configure-pages@v5 32 | - name: Build with Jekyll 33 | uses: actions/jekyll-build-pages@v1 34 | with: 35 | source: ./ 36 | destination: ./_site 37 | - name: Upload artifact 38 | uses: actions/upload-pages-artifact@v4 39 | 40 | # Deployment job 41 | deploy: 42 | environment: 43 | name: github-pages 44 | url: ${{ steps.deployment.outputs.page_url }} 45 | runs-on: ubuntu-latest 46 | needs: build 47 | steps: 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v4 51 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/util/MessageUtilTest.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.stream.IntStream; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | 10 | public class MessageUtilTest { 11 | 12 | @Test 13 | public void testNullCrop() { 14 | String s = null; 15 | assertNull(MessageUtil.cropS(s)); 16 | assertNull(MessageUtil.cropM(s)); 17 | assertNull(MessageUtil.cropL(s)); 18 | s = ""; 19 | assertTrue(MessageUtil.cropS(s).isEmpty()); 20 | assertTrue(MessageUtil.cropS(s).isEmpty()); 21 | } 22 | 23 | @Test 24 | public void testCrop() { 25 | String s = "abc"; 26 | assertEquals(s, MessageUtil.cropS(s)); 27 | assertEquals(s, MessageUtil.cropM(s)); 28 | assertEquals(s, MessageUtil.cropL(s)); 29 | 30 | StringBuilder sb = new StringBuilder(); 31 | IntStream.range(0, 65535).forEach(i -> sb.append("a")); 32 | assertEquals(65535, MessageUtil.cropL(sb.toString()).length()); 33 | StringBuilder sb1 = new StringBuilder(); 34 | IntStream.range(0, 65536).forEach(i -> sb1.append("a")); 35 | assertEquals(2075, MessageUtil.cropL(sb1.toString()).length()); 36 | StringBuilder sb2 = new StringBuilder(); 37 | IntStream.range(0, 165536).forEach(i -> sb2.append("a")); 38 | assertEquals(2075, MessageUtil.cropL(sb2.toString()).length()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/DefaultJsonComparator.java: -------------------------------------------------------------------------------- 1 | package io.json.compare; 2 | 3 | import java.util.Set; 4 | import java.util.regex.Pattern; 5 | import java.util.regex.PatternSyntaxException; 6 | 7 | public class DefaultJsonComparator implements JsonComparator { 8 | 9 | private final Set compareModes; 10 | 11 | public DefaultJsonComparator(Set compareModes) { 12 | this.compareModes = compareModes; 13 | } 14 | 15 | public boolean compareValues(Object expected, Object actual) { 16 | if (compareModes != null && compareModes.contains(CompareMode.REGEX_DISABLED)) { 17 | return expected.toString().equals(actual.toString()); 18 | } else { 19 | try { 20 | Pattern pattern = Pattern.compile(expected.toString(), Pattern.DOTALL | Pattern.MULTILINE); 21 | return pattern.matcher(actual.toString()).matches(); 22 | } catch (PatternSyntaxException e) { 23 | return expected.toString().equals(actual.toString()); 24 | } 25 | } 26 | } 27 | 28 | public boolean compareFields(String expected, String actual) { 29 | if (compareModes != null && compareModes.contains(CompareMode.REGEX_DISABLED)) { 30 | return expected.equals(actual); 31 | } else { 32 | try { 33 | Pattern pattern = Pattern.compile(expected, Pattern.DOTALL | Pattern.MULTILINE); 34 | return pattern.matcher(actual).matches(); 35 | } catch (PatternSyntaxException e) { 36 | return expected.equals(actual); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/matcher/JsonMatcher.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import io.json.compare.CompareMode; 5 | import io.json.compare.JsonComparator; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | public class JsonMatcher extends AbstractJsonMatcher { 13 | 14 | public JsonMatcher(JsonNode expected, JsonNode actual, JsonComparator comparator, Set compareModes) { 15 | super(expected, actual, comparator, compareModes); 16 | } 17 | 18 | @Override 19 | public List match() { 20 | if (isJsonObject(expected) && isJsonObject(actual)) { 21 | return new JsonObjectMatcher(expected, actual, comparator, compareModes).match(); 22 | } else if (isJsonArray(expected) && isJsonArray(actual)) { 23 | return new JsonArrayMatcher(expected, actual, comparator, compareModes).match(); 24 | } else if (isValueNode(expected) && isValueNode(actual)) { 25 | return new JsonValueMatcher(expected, actual, comparator, compareModes).match(); 26 | } else if (isJsonPathNode(expected)) { 27 | return new JsonObjectMatcher(expected, actual, comparator, compareModes).match(); 28 | } else if (isMissingNode(expected) && isMissingNode(actual)) { 29 | return Collections.emptyList(); 30 | } else { 31 | List diffs = new ArrayList<>(); 32 | diffs.add(" -> Different JSON types: expected " + expected.getClass().getSimpleName() + " but got " + actual.getClass().getSimpleName()); 33 | return diffs; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/util/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.util; 2 | 3 | import com.fasterxml.jackson.core.StreamReadConstraints; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; 8 | 9 | import java.io.IOException; 10 | 11 | public class JsonUtils { 12 | 13 | private JsonUtils() { 14 | 15 | } 16 | 17 | private static final ObjectMapper MAPPER = new ObjectMapper() 18 | .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) 19 | .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true) 20 | .configure(JsonNodeFeature.STRIP_TRAILING_BIGDECIMAL_ZEROES, false) 21 | .configure(DeserializationFeature.ACCEPT_FLOAT_AS_INT, false); 22 | 23 | static { 24 | MAPPER.getFactory().setStreamReadConstraints(StreamReadConstraints.builder() 25 | .maxNestingDepth(Integer.MAX_VALUE).maxNumberLength(Integer.MAX_VALUE).maxStringLength(Integer.MAX_VALUE).build()); 26 | } 27 | 28 | public static JsonNode toJson(Object obj) throws IOException { 29 | return obj instanceof JsonNode ? (JsonNode) obj : 30 | (obj instanceof String) ? MAPPER.readTree(obj.toString()) : MAPPER.convertValue(obj, JsonNode.class); 31 | } 32 | 33 | public static String prettyPrint(Object content) throws IOException { 34 | if (content instanceof String && content.toString().isEmpty()) { 35 | return ""; 36 | } 37 | return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(toJson(content)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/matcher/JsonValueMatcher.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import io.json.compare.CompareMode; 5 | import io.json.compare.JsonComparator; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | class JsonValueMatcher extends AbstractJsonMatcher { 12 | 13 | JsonValueMatcher(JsonNode expected, JsonNode actual, JsonComparator comparator, Set compareModes) { 14 | super(expected, actual, comparator, compareModes); 15 | } 16 | 17 | @Override 18 | public List match() { 19 | List diffs = new ArrayList<>(); 20 | String diff = System.lineSeparator() + "Expected %s: %s But got: %s"; 21 | 22 | if (expected.isNull() && !actual.isNull()) { 23 | diffs.add(String.format(diff, "null", "", actual)); 24 | return diffs; 25 | } else if (expected.isNumber() && !actual.isNumber()) { 26 | diffs.add(String.format(diff, "number", expected, actual)); 27 | return diffs; 28 | } else if (expected.isBoolean() && !actual.isBoolean()) { 29 | diffs.add(String.format(diff, "boolean", expected, actual)); 30 | return diffs; 31 | } else if (actual.isTextual() && !expected.isTextual()) { 32 | diffs.add(String.format(diff, "text", expected, actual)); 33 | return diffs; 34 | } else { 35 | UseCase useCase = getUseCase(expected.asText()); 36 | String expectedText = sanitize(expected.asText()); 37 | String actualText = actual.asText(); 38 | 39 | if (!useCase.equals(UseCase.MATCH_ANY) && comparator.compareValues(expectedText, actualText) != useCase.equals(UseCase.MATCH)) { 40 | diffs.add(String.format(diff, "value", expected, actual)); 41 | } 42 | return diffs; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONBooleanCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class JSONBooleanCompareTests { 7 | 8 | @Test 9 | public void compareSimple() { 10 | String expected = "{\"a\":true}"; 11 | String actual = "{\"a\":true}"; 12 | JSONCompare.assertMatches(expected, actual); 13 | } 14 | 15 | @Test 16 | public void compareSimple_negative() { 17 | String expected = "{\"a\":true}"; 18 | String actual = "{\"a\":false}"; 19 | JSONCompare.assertNotMatches(expected, actual); 20 | } 21 | 22 | @Test 23 | public void compareWithExpectedString() { 24 | String expected = "{\"a\":\"true\"}"; 25 | String actual = "{\"a\":true}"; 26 | JSONCompare.assertMatches(expected, actual); 27 | } 28 | 29 | @Test 30 | public void compareWithActualString() { 31 | String expected = "{\"a\":true}"; 32 | String actual = "{\"a\":\"true\"}"; 33 | JSONCompare.assertNotMatches(expected, actual); 34 | } 35 | 36 | @Test 37 | public void compareWithRegex() { 38 | String expected = "{\"a\":\"true|false\"}"; 39 | String actual = "{\"a\":true}"; 40 | JSONCompare.assertMatches(expected, actual); 41 | } 42 | 43 | @Test 44 | public void compareWithRegex_negative() { 45 | String expected = "{\"a\":\"false|text\"}"; 46 | String actual = "{\"a\":true}"; 47 | JSONCompare.assertNotMatches(expected, actual); 48 | } 49 | 50 | @Test 51 | public void compareViaDoNotMatchUseCase() { 52 | String expected = "{\"a\":\"!true\"}"; 53 | String actual = "{\"a\":false}"; 54 | JSONCompare.assertMatches(expected, actual); 55 | } 56 | 57 | @Test 58 | public void compareViaDoNotMatchUseCase_negative() { 59 | String expected = "{\"a\":\"!false\"}"; 60 | String actual = "{\"a\":false}"; 61 | JSONCompare.assertNotMatches(expected, actual); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/diffs/JsonRealWorldDiffTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.diffs; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | class JsonRealWorldDiffTests { 10 | 11 | @Test 12 | void compareJsonObjectsAndCheckForDifferences() { 13 | String expected = """ 14 | { 15 | "caught": false, 16 | "pain": { 17 | "range": [ 18 | "bell", 19 | "blue", 20 | -2059921070 21 | ], 22 | "not_anyone": -1760889549.4041045, 23 | "flat": -2099670336 24 | } 25 | } 26 | """; 27 | String actual = """ 28 | { 29 | "caught": true, 30 | "pain": { 31 | "range": [ 32 | "bell", 33 | "red", 34 | -2059921075 35 | ], 36 | "anyone": -1760889549.4041045, 37 | "flat": -2099670336 38 | }, 39 | "broad": "invented" 40 | } 41 | """; 42 | 43 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 44 | assertTrue(error.getMessage().matches("(?s).*FOUND 4 DIFFERENCE.*" + 45 | "\\Q$.caught\\E.*Expected value: false But got: true.*" + 46 | "\\Q$.pain.range[1]\\E was not found.*\"blue\".*" + 47 | "\\Q$.pain.range[2]\\E was not found.*-2059921070.*" + 48 | "\\Q$.pain.not_anyone\\E was not found.*")); 49 | JSONCompare.assertNotMatches(expected, actual); 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/matcher/JsonPathMatcher.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import com.fasterxml.jackson.core.StreamReadConstraints; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.jayway.jsonpath.Configuration; 8 | import com.jayway.jsonpath.JsonPath; 9 | import com.jayway.jsonpath.ParseContext; 10 | import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider; 11 | import io.json.compare.CompareMode; 12 | import io.json.compare.JsonComparator; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | class JsonPathMatcher extends AbstractJsonMatcher { 19 | 20 | private static final ObjectMapper MAPPER = new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); 21 | 22 | static { 23 | MAPPER.getFactory().setStreamReadConstraints(StreamReadConstraints.builder() 24 | .maxNestingDepth(Integer.MAX_VALUE).maxNumberLength(Integer.MAX_VALUE).maxStringLength(Integer.MAX_VALUE).build()); 25 | } 26 | 27 | private static final ParseContext PARSE_CONTEXT = JsonPath.using(new Configuration.ConfigurationBuilder() 28 | .jsonProvider(new JacksonJsonNodeJsonProvider()).build()); 29 | 30 | private final String jsonPath; 31 | 32 | JsonPathMatcher(String jsonPath, JsonNode expectedValue, JsonNode actual, JsonComparator comparator, Set compareModes) { 33 | super(expectedValue, actual, comparator, compareModes); 34 | this.jsonPath = jsonPath; 35 | } 36 | 37 | @Override 38 | public List match() { 39 | List diffs = new ArrayList<>(); 40 | JsonNode result = MAPPER.convertValue(PARSE_CONTEXT.parse(actual).read(jsonPath), JsonNode.class); 41 | List jsonPathDiffs = new JsonMatcher(expected, result, comparator, compareModes).match(); 42 | jsonPathDiffs.forEach(diff -> diffs.add(String.format("." + JSON_PATH_EXP_PREFIX + "%s" + JSON_PATH_EXP_SUFFIX + "%s" + System.lineSeparator() + "Expected json path result:" + 43 | System.lineSeparator() + "%s" + System.lineSeparator() + "But got:" + System.lineSeparator() + "%s" + System.lineSeparator(), 44 | jsonPath, diff, expected, result))); 45 | return diffs; 46 | } 47 | } -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue13Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.CompareMode; 4 | import io.json.compare.DefaultJsonComparator; 5 | import io.json.compare.JSONCompare; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.Set; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | public class Issue13Test { 14 | //Issue: https://github.com/fslev/json-compare/issues/28 15 | 16 | @Test 17 | // Check for hint message from failed comparison of jsons with unintentional regexes 18 | public void compareJsonWithUnintentionalRegexAndDefaultComparator() { 19 | String expected = "[{ \"name\" : \"someText (anotherText)\", \"code\" : \"oneMoreText\" }]"; 20 | String actual = "[{ \"name\" : \"someText (anotherText)\", \"code\" : \"oneMoreText\" }]"; 21 | try { 22 | JSONCompare.assertMatches(expected, actual); 23 | } catch (AssertionError e) { 24 | assertTrue(e.getMessage().contains("unintentional regexes")); 25 | } 26 | try { 27 | JSONCompare.assertMatches(expected, actual, new DefaultJsonComparator(null)); 28 | } catch (AssertionError e) { 29 | assertTrue(e.getMessage().contains("unintentional regexes")); 30 | assertTrue(e.getMessage().contains("disabling case-sensitivity")); 31 | } 32 | } 33 | 34 | @Test 35 | // Check hint message is missing from failed comparison with custom comparator 36 | public void compareJsonWithUnintentionalRegexAndCustomComparator() { 37 | String expected = "[{ \"name\" : \"someText (anotherText)\", \"code\" : \"oneMoreText\" }]"; 38 | String actual = "[{ \"name\" : \"someText (anotherText)\", \"code\" : \"oneMoreText\" }]"; 39 | try { 40 | JSONCompare.assertMatches(expected, actual, new CustomComparator(null)); 41 | } catch (AssertionError e) { 42 | assertFalse(e.getMessage().contains("unintentional regexes")); 43 | assertFalse(e.getMessage().contains("disabling case-sensitivity")); 44 | } 45 | } 46 | 47 | private static class CustomComparator extends DefaultJsonComparator { 48 | public CustomComparator(Set compareModes) { 49 | super(compareModes); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONValueNodeCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.node.IntNode; 6 | import com.fasterxml.jackson.databind.node.TextNode; 7 | import io.json.compare.JSONCompare; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertThrows; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | public class JSONValueNodeCompareTests { 14 | 15 | @Test 16 | public void compareTextNodes() { 17 | TextNode expected = new TextNode("some val"); 18 | TextNode actual = new TextNode("some val"); 19 | JSONCompare.assertMatches(expected, actual); 20 | } 21 | 22 | @Test 23 | public void compareEmptyTextNodes() { 24 | TextNode expected = new TextNode(""); 25 | TextNode actual = new TextNode(""); 26 | JSONCompare.assertMatches(expected, actual); 27 | } 28 | 29 | @Test 30 | public void compareMissingNodes() { 31 | JsonNode expected = new ObjectMapper().missingNode(); 32 | JsonNode actual = new ObjectMapper().missingNode(); 33 | JSONCompare.assertMatches(expected, actual); 34 | } 35 | 36 | @Test 37 | public void compareMissingNodes_negative() { 38 | JsonNode expected = new ObjectMapper().missingNode(); 39 | JsonNode actual = new TextNode(""); 40 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 41 | } 42 | 43 | @Test 44 | public void compareStringWithTextNode() { 45 | String expectedAsString = "\"some val\""; 46 | TextNode actual = new TextNode("some val"); 47 | JSONCompare.assertMatches(expectedAsString, actual, null, null, null); 48 | } 49 | 50 | @Test 51 | public void compareUnquotedStringWithTextNodeFails() { 52 | String expectedAsString = "some val"; 53 | TextNode actual = new TextNode("some val"); 54 | RuntimeException exception = assertThrows(RuntimeException.class, () -> JSONCompare.assertMatches(expectedAsString, actual)); 55 | assertTrue(exception.getMessage().contains("Invalid JSON")); 56 | } 57 | 58 | @Test 59 | public void compareIntNodes() { 60 | IntNode expected = new IntNode(1000000); 61 | IntNode actual = new IntNode(1000000); 62 | JSONCompare.assertMatches(expected, actual); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/util/JsonUtilsTest.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.util; 2 | 3 | import com.fasterxml.jackson.core.JsonParseException; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | public class JsonUtilsTest { 16 | 17 | @Test 18 | public void convertStringToJson() throws IOException { 19 | assertEquals(2, JsonUtils.toJson("{\"a\":2}").get("a").asInt()); 20 | } 21 | 22 | @Test 23 | public void convertObjectToJson() throws IOException { 24 | Map map = new HashMap<>(); 25 | map.put("a", 2); 26 | assertEquals(2, JsonUtils.toJson(map).get("a").asInt()); 27 | } 28 | 29 | @Test 30 | public void testBigJsonPrettyPrint() throws IOException { 31 | String json = readFromPath("bigJsons/actualLargeJson.json"); 32 | assertNotNull(JsonUtils.prettyPrint(json)); 33 | } 34 | 35 | @Test 36 | public void testEmptyJsonPrettyPrint() throws IOException { 37 | assertEquals("{\n" + 38 | " \"foo\" : \"bar\"\n" + 39 | "}", JsonUtils.prettyPrint("{\"foo\":\"bar\"}")); 40 | assertEquals("{ }", JsonUtils.prettyPrint("{}")); 41 | assertEquals("[ ]", JsonUtils.prettyPrint("[]")); 42 | assertEquals("null", JsonUtils.prettyPrint("null")); 43 | assertEquals("null", JsonUtils.prettyPrint(null)); 44 | assertThrows(JsonParseException.class, () -> JsonUtils.prettyPrint("invalid")); 45 | assertThrows(JsonParseException.class, () -> JsonUtils.prettyPrint("{invalid}")); 46 | assertThrows(JsonParseException.class, () -> JsonUtils.prettyPrint("{\"a\":0},1")); 47 | assertEquals("", JsonUtils.prettyPrint("")); 48 | } 49 | 50 | private static String readFromPath(String filePath) throws IOException { 51 | try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath); 52 | ByteArrayOutputStream result = new ByteArrayOutputStream()) { 53 | byte[] buffer = new byte[1024]; 54 | int length; 55 | while ((length = is.read(buffer)) != -1) { 56 | result.write(buffer, 0, length); 57 | } 58 | return result.toString(StandardCharsets.UTF_8.name()); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue12Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class Issue12Test { 7 | 8 | @Test 9 | public void testNegation() { 10 | String expected = "{\n" + 11 | " \"domainConnect\": {\n" + 12 | " \"ionos.com\": {\n" + 13 | " \"temporaryRedirectWithwww\": {\n" + 14 | " \"@\": {\n" + 15 | " \"startDate\": \"\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}.\\\\d{3}Z\",\n" + 16 | " \"endDate\": \"\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}.\\\\d{3}Z\"\n" + 17 | " },\n" + 18 | " \"host\": {\n" + 19 | " \"startDate\": \"\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}.\\\\d{3}Z\",\n" + 20 | " \"!endDate\": \".*\"\n" + 21 | " }\n" + 22 | " },\n" + 23 | " \"frameRedirectWithwww\": {\n" + 24 | " \"@\": {\n" + 25 | " \"startDate\": \"\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}.\\\\d{3}Z\",\n" + 26 | " \"!endDate\": \".*\"\n" + 27 | " }\n" + 28 | " }\n" + 29 | " }\n" + 30 | " }\n" + 31 | " }"; 32 | String actual = "{\n" + 33 | " \"domainConnect\" : {\n" + 34 | " \"ionos.com\" : {\n" + 35 | " \"temporaryRedirectWithwww\" : {\n" + 36 | " \"@\" : {\n" + 37 | " \"startDate\" : \"2020-04-21T09:56:18.960Z\",\n" + 38 | " \"endDate\" : \"2020-04-21T09:56:23.609Z\"\n" + 39 | " },\n" + 40 | " \"host\" : {\n" + 41 | " \"startDate\" : \"2020-04-21T09:56:18.960Z\"\n" + 42 | " }\n" + 43 | " },\n" + 44 | " \"frameRedirectWithwww\" : {\n" + 45 | " \"@\" : {\n" + 46 | " \"startDate\" : \"2020-04-21T09:56:23.576Z\"\n" + 47 | " }\n" + 48 | " }\n" + 49 | " }\n" + 50 | " }\n" + 51 | "}"; 52 | JSONCompare.assertMatches(expected, actual); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/diffs/JsonValueDiffTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.diffs; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | public class JsonValueDiffTests { 10 | 11 | @Test 12 | public void compareNulls() { 13 | String expected = "{\"a\":null}"; 14 | String actual = "{\"a\":\"null\"}"; 15 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 16 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*\\Q$.a\\E.*Expected null: But got: \"null\".*")); 17 | JSONCompare.assertNotMatches(expected, actual); 18 | } 19 | 20 | @Test 21 | public void compareBooleans() { 22 | String expected = "{\"a\":false}"; 23 | String actual = "{\"a\":\"false\"}"; 24 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 25 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*\\Q$.a\\E.*Expected boolean: false But got: \"false\".*")); 26 | JSONCompare.assertNotMatches(expected, actual); 27 | } 28 | 29 | @Test 30 | public void compareNumbers() { 31 | String expected = "{\"a\":2}"; 32 | String actual = "{\"a\":\"2\"}"; 33 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 34 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*\\Q$.a\\E.*Expected number: 2 But got: \"2\".*")); 35 | JSONCompare.assertNotMatches(expected, actual); 36 | } 37 | 38 | @Test 39 | public void compareAll() { 40 | String expected = "{\"a\":null,\"b\":1,\"c\":false,\"d\":\"false\",\"e\":\"text\",\"f\":13432.543,\"f1\":13432.543}"; 41 | String actual = "{\"a\":\"null\",\"b\":\"1\",\"c\":\"false\",\"d\":false,\"e\":false,\"f\":13432.543,\"f1\":\"13432.543\"}"; 42 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 43 | assertTrue(error.getMessage().matches("(?s).*FOUND 5 DIFFERENCE.*" + 44 | "\\Q$.a\\E.*Expected null: But got: \"null\".*" + 45 | "\\Q$.b\\E.*Expected number: 1 But got: \"1\".*" + 46 | "\\Q$.c\\E.*Expected boolean: false But got: \"false\".*" + 47 | "\\Q$.e\\E.*Expected value: \"text\" But got: false.*" + 48 | "\\Q$.f1\\E.*Expected number: 13432.543 But got: \"13432.543\".*")); 49 | JSONCompare.assertNotMatches(expected, actual); 50 | } 51 | 52 | @Test 53 | public void compareAll_positive() { 54 | String expected = "{\"a\":null,\"b\":1,\"c\":false,\"d\":\"false\",\"e\":\"text\",\"f\":13432.543,\"f1\":13430.143}"; 55 | String actual = "{\"a\":null,\"b\":1,\"c\":false,\"d\":false,\"e\":\"text\",\"f\":13432.543,\"f1\":13430.143}"; 56 | JSONCompare.assertMatches(expected, actual); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONNullCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class JSONNullCompareTests { 7 | 8 | @Test 9 | public void compareWithNullValue() { 10 | String expected = "{\"a\":null}"; 11 | String actual = "{\"a\":null}"; 12 | JSONCompare.assertMatches(expected, actual); 13 | } 14 | 15 | @Test 16 | public void compareWithNullTextValue() { 17 | String expected = "{\"a\":\"null\"}"; 18 | String actual = "{\"a\":\"null\"}"; 19 | JSONCompare.assertMatches(expected, actual); 20 | } 21 | 22 | @Test 23 | public void compareWithNullTextValue_negative() { 24 | String expected = "{\"a\":\"null\"}"; 25 | String actual = "{\"a\":null}"; 26 | JSONCompare.assertMatches(expected, actual); 27 | } 28 | 29 | @Test 30 | public void compareWithRegex() { 31 | String expected = "{\"a\":\"null|text\"}"; 32 | String actual = "{\"a\":null}"; 33 | JSONCompare.assertMatches(expected, actual); 34 | } 35 | 36 | @Test 37 | public void compareWithRegex_negative() { 38 | String expected = "{\"a\":\"false|text\"}"; 39 | String actual = "{\"a\":null}"; 40 | JSONCompare.assertNotMatches(expected, actual); 41 | } 42 | 43 | @Test 44 | public void compareByNegativeUseCase() { 45 | String expected = "{\"a\":\"!null\"}"; 46 | String actual = "{\"a\":\"txt\"}"; 47 | JSONCompare.assertMatches(expected, actual); 48 | } 49 | 50 | @Test 51 | public void compareByNegativeUseCase_negative() { 52 | String expected = "{\"a\":\"!null\"}"; 53 | String actual = "{\"a\":null}"; 54 | JSONCompare.assertNotMatches(expected, actual); 55 | } 56 | 57 | @Test 58 | public void compareArrays() { 59 | String expected = "[null]"; 60 | String actual = "[null]"; 61 | JSONCompare.assertMatches(expected, actual); 62 | } 63 | 64 | @Test 65 | public void compareArrays_negative() { 66 | String expected = "[null]"; 67 | String actual = "[2]"; 68 | JSONCompare.assertNotMatches(expected, actual); 69 | } 70 | 71 | @Test 72 | public void compareArraysWithExpectedNullAsString() { 73 | String expected = "[\"null\"]"; 74 | String actual = "[\"null\"]"; 75 | JSONCompare.assertMatches(expected, actual); 76 | } 77 | 78 | @Test 79 | public void compareArraysWithExpectedNullAsString_negative() { 80 | String expected = "[\"null\"]"; 81 | String actual = "[null]"; 82 | JSONCompare.assertMatches(expected, actual); 83 | } 84 | 85 | @Test 86 | public void compareArraysWithActualNullAsString_negative() { 87 | String expected = "[null]"; 88 | String actual = "[\"null\"]"; 89 | JSONCompare.assertNotMatches(expected, actual); 90 | } 91 | 92 | @Test 93 | public void compareArraysWithExpectedDoNotFindNull() { 94 | String expected = "[\"!null\"]"; 95 | String actual = "[null]"; 96 | JSONCompare.assertNotMatches(expected, actual); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 7.2 (2025-11-10) 4 | - #### Changed 5 | - Updated dependencies 6 | 7 | ## 7.1 (2025-08-01) 8 | - #### Changed 9 | - Updated dependencies 10 | 11 | ## 7.0 (2025-03-28) 12 | - #### Changed 13 | - Changed style for JSON diffs 14 | 15 | ## 6.18 (2024-09-26) 16 | - #### Changed 17 | - Aligned Jackson dependencies versions via Jackson BOM 18 | 19 | ## 6.17 (2024-09-26) 20 | - #### Changed 21 | - Updated Junit 5 22 | - Aligned Junit 5 dependencies versions via the JUnit Platform BOM 23 | 24 | ## 6.16 (2024-09-20) 25 | - #### Changed 26 | - Updated dependencies 27 | 28 | ## 6.15 (2024-05-22) 29 | - #### Changed 30 | - Updated dependencies 31 | 32 | ## 6.14 (2024-03-22) 33 | - #### Changed 34 | - Updated dependencies 35 | 36 | ## 6.13 (2023-11-26) 37 | - #### Changed 38 | - Update dependencies (Jackson) 39 | 40 | ## 6.12 (2023-11-15) 41 | - #### Changed 42 | - Update dependencies 43 | 44 | ## 6.11 (2023-09-15) 45 | - #### Changed 46 | - Update dependencies (junit-jupiter-api to v5.10.0) 47 | 48 | ## 6.10 (2023-06-03) 49 | - #### Changed 50 | - Added support to retrieve the differences as a List of Strings 51 | - `List diffs = JSONCompare.diffs(expected, actual);` 52 | - Update dependencies (Jackson 2.15.2) 53 | 54 | 55 | ## 6.9 (2023-04-27) 56 | - #### Changed 57 | - Use Integer.MAX_VALUE for ObjectMapper max number, nesting and String length 58 | 59 | ## 6.8 (2023-04-26) 60 | - #### Changed 61 | - Update dependencies (junit jupiter 5.9.3, Jackson 2.15.0, JsonPath 2.8.0) 62 | 63 | ## 6.7 (2023-01-10) 64 | - #### Changed 65 | - Update dependencies (junit jupiter 5.9.2) 66 | 67 | ## 6.6 (2022-12-31) 68 | - #### Changed 69 | - Defined private constructors for utility classes 70 | - Code refactoring 71 | 72 | ## 6.5 (2022-12-22) 73 | - #### Fixed 74 | - Case-sensitive match of JSON values and fields containing invalid regexes 75 | 76 | ## 6.4 (2022-12-19) 77 | - #### Changed 78 | - Added new compare mode: CompareMode.REGEX_DISABLED 79 | - JSONs can be matched without using regular expressions: any present regexes will be considered as normal text 80 | - Refactoring - optimize imports 81 | 82 | ## 6.3 (2022-11-24) 83 | - #### Changed 84 | - Update dependencies (Jackson 2.14.1) 85 | 86 | ## 6.2 (2022-11-15) 87 | - #### Changed 88 | - Describe the default case-sensitive matching behaviour inside the AssertionError Hint message 89 | 90 | ## 6.1 (2022-11-14) 91 | - #### Fixed 92 | - Matched JSON fields are also case-sensitive, by default 93 | 94 | ## 6.0 (2022-11-14) 95 | - #### Changed 96 | - By default, matched JSONs are case-sensitive 97 | 98 | ## 5.3 (2022-11-11) 99 | - #### Changed 100 | - Small code refactoring 101 | - Jackson dependencies update 102 | 103 | ## 5.2 (2022-10-31) 104 | - #### Changed 105 | - refactor AssertionError messages 106 | - #### Fixed 107 | - MATCH_ANY and DO_NOT_MATCH_ANY use cases now work as expected while using a custom comparator 108 | - DO_NOT_MATCH_ANY use case now works in all cases 109 | 110 | ## 5.1 (2022-10-05) 111 | - #### Changed 112 | - refactor DO_NOT_MATCH_ANY use case behaviour for JSON arrays 113 | 114 | ## 5.0 (2022-10-04) 115 | - #### Added 116 | - Differences support 117 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONConvertibleObjectsCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.CompareMode; 4 | import io.json.compare.JSONCompare; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.math.BigDecimal; 8 | import java.math.BigInteger; 9 | import java.util.Arrays; 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Map; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertThrows; 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | 17 | public class JSONConvertibleObjectsCompareTests { 18 | 19 | @Test 20 | public void compareMaps() { 21 | String expected = "{\"a\":1,\"b\":[4,2,\"\\\\d+\"]}"; 22 | Map actual = new HashMap<>(); 23 | actual.put("a", 1); 24 | actual.put("b", Arrays.asList(1, 2, 3, 4)); 25 | JSONCompare.assertMatches(expected, actual); 26 | JSONCompare.assertNotMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_STRICT_ORDER))); 27 | JSONCompare.assertNotMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE))); 28 | JSONCompare.assertMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE))); 29 | 30 | Map expectedMap = new HashMap<>(); 31 | expectedMap.put("a", "1"); 32 | expectedMap.put("b", Arrays.asList(4, 2, "\\d+")); 33 | JSONCompare.assertMatches(expectedMap, actual); 34 | JSONCompare.assertNotMatches(expectedMap, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_STRICT_ORDER))); 35 | JSONCompare.assertNotMatches(expectedMap, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE))); 36 | JSONCompare.assertMatches(expectedMap, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE))); 37 | } 38 | 39 | @Test 40 | public void checkJsonConversionErrorMessage() { 41 | String expected = "{\"a\":1,\"b\":[4,2,d]}"; 42 | Map actual = new HashMap<>(); 43 | actual.put("a", 1); 44 | RuntimeException e = assertThrows(RuntimeException.class, () -> JSONCompare.assertMatches(expected, actual)); 45 | assertTrue(e.getMessage().contains("Invalid JSON")); 46 | RuntimeException e1 = assertThrows(RuntimeException.class, () -> JSONCompare.assertMatches("{\"a\":1}", "{\"a:1}")); 47 | assertTrue(e1.getMessage().contains("Invalid JSON")); 48 | RuntimeException e2 = assertThrows(RuntimeException.class, () -> JSONCompare.assertNotMatches("{\"a\":1}", "{\"a:2}")); 49 | assertTrue(e2.getMessage().contains("Invalid JSON")); 50 | } 51 | 52 | @Test 53 | public void compareNumbers() { 54 | BigDecimal expectedBigDec = new BigDecimal(20000); 55 | BigDecimal actualBigDec = new BigDecimal(20000L); 56 | JSONCompare.assertMatches(expectedBigDec, actualBigDec); 57 | BigInteger expectedBigInt = new BigInteger("20000000"); 58 | BigInteger actualBigInt = new BigInteger("20000000"); 59 | JSONCompare.assertMatches(expectedBigInt, actualBigInt); 60 | Double expectedDouble = 0.12454543D; 61 | Double actualDouble = 0.12454543D; 62 | JSONCompare.assertMatches(expectedDouble, actualDouble); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue10Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class Issue10Test { 7 | 8 | @Test 9 | public void testMultipleNegationInJsonArray() { 10 | String expected = "{\"statuses\":[ \"!a.*\",\"!b.*\" ]}"; 11 | String actual = "{\"statuses\":[\"abc\",\"cde\"]}"; 12 | JSONCompare.assertNotMatches(expected, actual); 13 | } 14 | 15 | @Test 16 | public void testFieldNameDoesntExist() { 17 | String expected = "{\"(?!autoRenew).*\":true}"; 18 | String actual = "{\"autoRene\": true}"; 19 | JSONCompare.assertMatches(expected, actual); 20 | } 21 | 22 | @Test 23 | public void testFieldNameDoesntExist_negative() { 24 | String expected = "{\"(?!autoRenew).*\":true}"; 25 | String actual = "{\"autoRenew\": true}"; 26 | JSONCompare.assertNotMatches(expected, actual); 27 | } 28 | 29 | @Test 30 | public void compareJsonArraysInsideJsonObject() { 31 | String expected = "{\n" + 32 | " \"name\" : \"e2e-customer-api-dns-test2.de\",\n" + 33 | " \"type\" : \"NATIVE\",\n" + 34 | " \"id\" : \"8e529591-1cd5-11ea-a124-0a586444b21a\",\n" + 35 | " \"records\" : [ {\n" + 36 | " \"!recordId\" : \".*\",\n" + 37 | " \"id\" : \".{36}\",\n" + 38 | " \"name\" : \"e2e-customer-api-dns-test2.de\",\n" + 39 | " \"type\" : \"MX\",\n" + 40 | " \"content\" : \".*\",\n" + 41 | " \"disabled\" : false\n" + 42 | " }, {\n" + 43 | " \"!recordId\" : \".*\",\n" + 44 | " \"id\" : \".{36}\",\n" + 45 | " \"name\" : \"mail.e2e-customer-api-dns-test2.de\",\n" + 46 | " \"type\" : \"MX\",\n" + 47 | " \"content\" : \".*\",\n" + 48 | " \"disabled\" : false\n" + 49 | " }, \"!.*\" ]\n" + 50 | "}"; 51 | String actual = "{\n" + 52 | " \"name\" : \"e2e-customer-api-dns-test2.de\",\n" + 53 | " \"id\" : \"8e529591-1cd5-11ea-a124-0a586444b21a\",\n" + 54 | " \"type\" : \"NATIVE\",\n" + 55 | " \"records\" : [ {\n" + 56 | " \"name\" : \"mail.e2e-customer-api-dns-test2.de\",\n" + 57 | " \"rootName\" : \"e2e-customer-api-dns-test2.de\",\n" + 58 | " \"type\" : \"MX\",\n" + 59 | " \"content\" : \"mx.sub.updated.server.lan\",\n" + 60 | " \"changeDate\" : \"2019-12-19T13:54:25.000Z\",\n" + 61 | " \"ttl\" : 60,\n" + 62 | " \"prio\" : 11,\n" + 63 | " \"disabled\" : false,\n" + 64 | " \"id\" : \"9954cc0f-0ac8-b589-37a5-987da68734d0\"\n" + 65 | " }, {\n" + 66 | " \"name\" : \"e2e-customer-api-dns-test2.de\",\n" + 67 | " \"rootName\" : \"e2e-customer-api-dns-test2.de\",\n" + 68 | " \"type\" : \"MX\",\n" + 69 | " \"content\" : \"mx.patched.server.lan\",\n" + 70 | " \"changeDate\" : \"2019-12-19T13:54:27.000Z\",\n" + 71 | " \"ttl\" : 3000,\n" + 72 | " \"prio\" : 100,\n" + 73 | " \"disabled\" : false,\n" + 74 | " \"id\" : \"74e7cacd-7062-7f0d-36a6-7f0b6d3a733c\"\n" + 75 | " } ]\n" + 76 | "}"; 77 | JSONCompare.assertMatches(expected, actual); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue6Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | 8 | public class Issue6Test { 9 | 10 | @Test 11 | public void testIssue1() { 12 | String expected = "[" + 13 | "{\n" + 14 | " \"name\" : \"division1\",\n" + 15 | " \"vlan\" : \"116\"\n" + 16 | "}," + 17 | "{\n" + 18 | " \"name\" : \"!division1\",\n" + 19 | " \"vlan\" : \"!115\"\n" + 20 | "}]"; 21 | String actual = "[{\n" + 22 | " \"name\" : \"division1\",\n" + 23 | " \"vlan\" : \"116\"\n" + 24 | "}, {" + 25 | " \"name\" : \"division2\",\n" + 26 | " \"vlan\" : \"117\"" + 27 | "}]"; 28 | JSONCompare.assertMatches(expected, actual); 29 | } 30 | 31 | 32 | @Test 33 | public void testIssue1a() { 34 | String expected = "[{\n" + 35 | " \"name\" : \"division1\",\n" + 36 | " \"vlan\" : \"116\"\n" + 37 | "}," + 38 | "{\n" + 39 | " \"!name\" : \"division1\",\n" + 40 | " \"!vlan\" : \"115\"\n" + 41 | "}]"; 42 | String actual = "[{\n" + 43 | " \"name\" : \"division1\",\n" + 44 | " \"vlan\" : \"116\"\n" + 45 | "}," + 46 | "{\n" + 47 | " \"a\" : \"division2\",\n" + 48 | " \"b\" : \"114\"\n" + 49 | "}]"; 50 | JSONCompare.assertMatches(expected, actual); 51 | } 52 | 53 | @Test 54 | public void testIssue1a_negative() { 55 | String expected = "[{\n" + 56 | " \"name\" : \"division1\",\n" + 57 | " \"vlan\" : \"116\"\n" + 58 | "}," + 59 | "{\n" + 60 | " \"!name\" : \"division1\",\n" + 61 | " \"!vlan\" : \"115\"\n" + 62 | "}]"; 63 | String actual = "[{\n" + 64 | " \"name\" : \"division1\",\n" + 65 | " \"vlan\" : \"116\"\n" + 66 | "}," + 67 | "{\n" + 68 | " \"a\" : \"division2\",\n" + 69 | " \"vlan\" : \"114\"\n" + 70 | "}]"; 71 | JSONCompare.assertNotMatches(expected, actual); 72 | } 73 | 74 | @Test 75 | public void testIssue1_negative() { 76 | String expected = "[{\n" + 77 | " \"name\" : \"division1\",\n" + 78 | " \"vlan\" : \"116\"\n" + 79 | "}," + 80 | "{\n" + 81 | " \"!name\" : \"division1\",\n" + 82 | " \"!vlan\" : \"115\"\n" + 83 | "}]"; 84 | String actual = "[{\n" + 85 | " \"name\" : \"division1\",\n" + 86 | " \"vlan\" : \"116\"\n" + 87 | "}," + 88 | "{\n" + 89 | " \"name\" : \"division1\",\n" + 90 | " \"vlan\" : \"115\"\n" + 91 | "}]"; 92 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 93 | } 94 | 95 | 96 | @Test 97 | public void testIssue2() { 98 | String expected = "[" + 99 | "{\n" + 100 | " \"!name\" : \"division1\",\n" + 101 | " \"!vlan\" : \"115\"\n" + 102 | "}]"; 103 | String actual = "[{\n" + 104 | " \"names\" : \"division1\",\n" + 105 | " \"vlan\" : \"116\"\n" + 106 | "}]"; 107 | JSONCompare.assertNotMatches(expected, actual); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue8Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | 8 | public class Issue8Test { 9 | 10 | @Test 11 | public void testJsonObjectMatchUseCase() { 12 | String expected = "{\"records\":[ {\"a\":0} ]}"; 13 | String actual = "{\"records\":[ true, {\"a\":0} ]}"; 14 | JSONCompare.assertMatches(expected, actual); 15 | } 16 | 17 | @Test 18 | public void testJsonObjectMatchUseCase_negative() { 19 | String expected = "{\"records\":[ {\"a\":0} ]}"; 20 | String actual = "{\"records\":[ {\"a\":1} ]}"; 21 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 22 | } 23 | 24 | @Test 25 | public void testJsonArrayMatchUseCase() { 26 | String expected = "{\"records\":[ 1,true, \"b\", {\"a\": 0}, [2] ]}"; 27 | String actual = "{\"records\":[ \"b\", {\"a\": 0}, true, [2], 1 ]}"; 28 | JSONCompare.assertMatches(expected, actual); 29 | } 30 | 31 | @Test 32 | public void testJsonArrayMatchUseCase_negative() { 33 | String expected = "{\"records\":[ 1,true, \"b\", {\"a\": 0}, [2] ]}"; 34 | String actual = "{\"records\":[ \"b\", {\"b\": 0}, true, [2], 1 ]}"; 35 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 36 | } 37 | 38 | @Test 39 | public void testJsonObjectDoNotMatchUseCase() { 40 | String expected = "{\"records\":[ {\"!a\":0} ]}"; 41 | String actual = "{\"records\":[ true, {\"b\":0} ]}"; 42 | JSONCompare.assertMatches(expected, actual); 43 | } 44 | 45 | @Test 46 | public void testJsonObjectDoNotMatchUseCase_negative() { 47 | String expected = "{\"records\":[ {\"!a\":0} ]}"; 48 | String actual = "{\"records\":[ {\"a\":1} ]}"; 49 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 50 | } 51 | 52 | @Test 53 | public void testJsonArrayDoNotMatchUseCase() { 54 | String expected = "{\"records\":[ [\"!c\"] ]}"; 55 | String actual = "{\"records\":[ \"b\", {\"a\": 0}, true, [2], 1 ]}"; 56 | JSONCompare.assertMatches(expected, actual); 57 | } 58 | 59 | @Test 60 | public void testJsonArrayDoNotMatchUseCase1() { 61 | String expected = "{\"records\":[ [\"!c\"] ]}"; 62 | String actual = "{\"records\":[ \"b\", {\"a\": 0}, true, 1 ]}"; 63 | JSONCompare.assertNotMatches(expected, actual); 64 | } 65 | 66 | @Test 67 | public void testJsonArrayDoNotMatchUseCase_negative1() { 68 | String expected = "{\"records\":[ [\"!c\"] ]}"; 69 | String actual = "{\"records\":[ \"b\", {\"a\": 0}, [\"c\"], true, 1 ]}"; 70 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 71 | } 72 | 73 | @Test 74 | public void testDoNotMatchUseCaseCompareEscaped() { 75 | String expected = "{\"\\\\!records\":0}"; 76 | String actual = "{\"!records\":0}"; 77 | JSONCompare.assertMatches(expected, actual); 78 | } 79 | 80 | @Test 81 | public void testDoNotMatchUseCaseCompareEscaped1() { 82 | String expected = "{\"\\\\Q\\\\!records\\\\E\":0}"; 83 | String actual = "{\"\\\\!records\":0}"; 84 | JSONCompare.assertMatches(expected, actual); 85 | } 86 | 87 | @Test 88 | public void testDoNotMatchAnyUseCase() { 89 | String expected = "{\"records\":[ \"!.*\" ]}"; 90 | String actual = "{\"records\":[ ]}"; 91 | JSONCompare.assertMatches(expected, actual); 92 | } 93 | 94 | @Test 95 | public void testDoNotMatchAnyUseCase_negative() { 96 | String expected = "{\"records\":[ \"!.*\" ]}"; 97 | String actual = "{\"records\":{}}"; 98 | JSONCompare.assertNotMatches(expected, actual); 99 | } 100 | 101 | @Test 102 | public void testDoNotMatchAnyUseCase_negative_A() { 103 | String expected = "{\"records\":[ \"!.*\" ]}"; 104 | String actual = "{\"records\": [true]}"; 105 | JSONCompare.assertNotMatches(expected, actual); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONArrayCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | 7 | public class JSONArrayCompareTests { 8 | 9 | @Test 10 | public void compareEmptyArrays() { 11 | String expected = "[]"; 12 | String actual = "[]"; 13 | JSONCompare.assertMatches(expected, actual); 14 | } 15 | 16 | @Test 17 | public void compareEmptyArrays_negative() { 18 | String expected = "[1]"; 19 | String actual = "[]"; 20 | JSONCompare.assertNotMatches(expected, actual); 21 | } 22 | 23 | @Test 24 | public void compareEmptyArrays_negative1() { 25 | String expected = "[{}]"; 26 | String actual = "[]"; 27 | Assertions.assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 28 | JSONCompare.assertNotMatches(expected, actual); 29 | } 30 | 31 | @Test 32 | public void compareArraysWithDuplicatedElements() { 33 | String expected = "[2,2,4,4,4]"; 34 | String actual = "[4,4,4,4,2,2]"; 35 | JSONCompare.assertMatches(expected, actual); 36 | } 37 | 38 | @Test 39 | public void compareArraysWithDuplicatedElements_negative() { 40 | String expected = "[2,2,2,4,4,4]"; 41 | String actual = "[4,4,4,4,2,2]"; 42 | JSONCompare.assertNotMatches(expected, actual); 43 | } 44 | 45 | @Test 46 | public void compareSimple() { 47 | String expected = "[\"val1\",\"val2\"]"; 48 | String actual = "[\"val2\",\"val1\"]"; 49 | JSONCompare.assertMatches(expected, actual); 50 | } 51 | 52 | @Test 53 | public void compareSimple_negative() { 54 | String expected = "[\"val1\",\"val2\"]"; 55 | String actual = "[\"val2\",\"val3\"]"; 56 | JSONCompare.assertNotMatches(expected, actual); 57 | } 58 | 59 | @Test 60 | public void compareDoNotFindElement() { 61 | String expected = "[\"!val1\",\"val2\"]"; 62 | String actual = "[\"val2\",\"val2\"]"; 63 | JSONCompare.assertMatches(expected, actual); 64 | } 65 | 66 | @Test 67 | public void compareDoNotFindElement_negative() { 68 | String expected = "[\"!val1\",\"val2\"]"; 69 | String actual = "[\"val2\",\"val1\"]"; 70 | JSONCompare.assertNotMatches(expected, actual); 71 | } 72 | 73 | @Test 74 | public void compareWithNoExtraElements() { 75 | String expected = "[1,\"test\",4,\"!.*\"]"; 76 | String actual = "[4,1,\"test\"]"; 77 | JSONCompare.assertMatches(expected, actual); 78 | } 79 | 80 | @Test 81 | public void compareWithNoExtraElements1() { 82 | String expected = "[1,\"test\",\"!.*\",4]"; 83 | String actual = "[4,1,\"test\"]"; 84 | JSONCompare.assertMatches(expected, actual); 85 | } 86 | 87 | @Test 88 | public void compareWithNoExtraElements_negative1() { 89 | String expected = "[1,\"test\",4,\"!.*\"]"; 90 | String actual = "[4,1,\"test\", {\"a\":0}]"; 91 | JSONCompare.assertNotMatches(expected, actual); 92 | } 93 | 94 | @Test 95 | public void compareWithNoExtraElements_negative2() { 96 | String expected = "[1,\"test\",4,\"!.*\"]"; 97 | String actual = "[4,1,\"test\",true]"; 98 | JSONCompare.assertNotMatches(expected, actual); 99 | } 100 | 101 | @Test 102 | public void compareWithExtraElements() { 103 | String expected = "[1,\"test\",4,\".*\"]"; 104 | String actual = "[4,1,\"test\",false]"; 105 | JSONCompare.assertMatches(expected, actual); 106 | } 107 | 108 | @Test 109 | public void compareWithExtraElements_negative() { 110 | String expected = "[1,\"test\",4,\".*\"]"; 111 | String actual = "[4,1,\"test\"]"; 112 | JSONCompare.assertNotMatches(expected, actual); 113 | } 114 | 115 | @Test 116 | public void deepCompareJsonArray() { 117 | String expected = "[\"val1\",\"val2\",[10,[\"val3\"],10,false]]"; 118 | String actual = "[\"val2\",\"val1\",[10,10,false,[\"val3\"]]]"; 119 | JSONCompare.assertMatches(expected, actual); 120 | } 121 | 122 | @Test 123 | public void deepCompareJsonArray_negative() { 124 | String expected = "[\"val1\",\"val2\",[10,10,false,[\"val3\"]]]"; 125 | String actual = "[\"val2\",\"val1\",[10,10,false,[\"notval3\"]]]"; 126 | JSONCompare.assertNotMatches(expected, actual); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/matcher/JsonArrayMatcher.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import io.json.compare.CompareMode; 5 | import io.json.compare.JSONCompare; 6 | import io.json.compare.JsonComparator; 7 | import io.json.compare.util.MessageUtil; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | class JsonArrayMatcher extends AbstractJsonMatcher { 16 | 17 | private final Set matchedPositions = new HashSet<>(); 18 | 19 | JsonArrayMatcher(JsonNode expected, JsonNode actual, JsonComparator comparator, Set compareModes) { 20 | super(expected, actual, comparator, compareModes); 21 | } 22 | 23 | @Override 24 | public List match() { 25 | List diffs = new ArrayList<>(); 26 | 27 | for (int i = 0; i < expected.size(); i++) { 28 | JsonNode expElement = expected.get(i); 29 | UseCase useCase = getUseCase(expElement); 30 | if (isJsonPathNode(expElement)) { 31 | diffs.addAll(new JsonMatcher(expElement, actual, comparator, compareModes).match()); 32 | } else { 33 | diffs.addAll(matchWithJsonArray(i, expElement, useCase, actual)); 34 | } 35 | } 36 | if (compareModes.contains(CompareMode.JSON_ARRAY_NON_EXTENSIBLE) && expected.size() - getDoNotMatchUseCases(expected) < actual.size()) { 37 | diffs.add(" -> Actual JSON ARRAY has extra elements"); 38 | } 39 | return diffs; 40 | } 41 | 42 | private List matchWithJsonArray(int expPosition, JsonNode expElement, UseCase useCase, JsonNode actualArray) { 43 | List diffs = new ArrayList<>(); 44 | 45 | for (int j = 0; j < actualArray.size(); j++) { 46 | if (matchedPositions.contains(j)) { 47 | continue; 48 | } 49 | if (compareModes.contains(CompareMode.JSON_ARRAY_STRICT_ORDER)) { 50 | if (j < expPosition) { 51 | continue; 52 | } else if (j > expPosition) { 53 | break; 54 | } 55 | } 56 | List elementDiffs; 57 | switch (useCase) { 58 | case MATCH: 59 | JsonNode actElement = actualArray.get(j); 60 | elementDiffs = new JsonMatcher(expElement, actElement, comparator, compareModes).match(); 61 | if (elementDiffs.isEmpty()) { 62 | matchedPositions.add(j); 63 | return Collections.emptyList(); 64 | } else { 65 | if (compareModes.contains(CompareMode.JSON_ARRAY_STRICT_ORDER)) { 66 | elementDiffs.forEach(elementDiff -> 67 | diffs.add(String.format("[%s]%s", expPosition, elementDiff)) 68 | ); 69 | return diffs; 70 | } 71 | } 72 | break; 73 | case MATCH_ANY: 74 | matchedPositions.add(j); 75 | return Collections.emptyList(); 76 | case DO_NOT_MATCH: 77 | actElement = actualArray.get(j); 78 | if (areOfSameType(expElement, actElement)) { 79 | elementDiffs = new JsonMatcher(expElement, actElement, comparator, compareModes).match(); 80 | if (!elementDiffs.isEmpty()) { 81 | diffs.add("[" + expPosition + "]" 82 | + " was found:" + System.lineSeparator() + MessageUtil.cropL(JSONCompare.prettyPrint(expElement))); 83 | return diffs; 84 | } 85 | } 86 | break; 87 | case DO_NOT_MATCH_ANY: 88 | if (expected.size() - getDoNotMatchUseCases(expected) < actual.size()) { 89 | diffs.add(String.format("[%s] -> Expected condition %s was not met." + 90 | " Actual JSON ARRAY has extra elements", expPosition, expElement)); 91 | } 92 | return diffs; 93 | } 94 | } 95 | if (useCase == UseCase.MATCH) { 96 | diffs.add("[" + expPosition + "] was not found:" + System.lineSeparator() 97 | + MessageUtil.cropL(JSONCompare.prettyPrint(expElement))); 98 | } else if (useCase == UseCase.MATCH_ANY) { 99 | diffs.add(String.format("[%s] -> Expected condition %s was not met." + 100 | " Actual JSON ARRAY has no extra elements", expPosition, expElement)); 101 | } 102 | return diffs; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONCompareMessageTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.CompareMode; 4 | import io.json.compare.JSONCompare; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.HashSet; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertThrows; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | public class JSONCompareMessageTests { 15 | 16 | @Test 17 | public void checkMessageFromJSONObjectFailedCompare() { 18 | String expected = "{\"a\":true}"; 19 | String actual = "{\"ab\":true}"; 20 | try { 21 | JSONCompare.assertMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE)), "JSONs do match"); 22 | } catch (AssertionError e) { 23 | assertTrue(e.getMessage().contains("JSONs do match")); 24 | } 25 | } 26 | 27 | @Test 28 | public void checkMessageFromJSONObjectFailedCompare_1() { 29 | String expected = "{\"a\":true}"; 30 | String actual = "{\"ab\":true}"; 31 | try { 32 | JSONCompare.assertMatches(expected, actual, null, new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE)), "JSONs do match"); 33 | } catch (AssertionError e) { 34 | assertTrue(e.getMessage().contains("JSONs do match")); 35 | } 36 | } 37 | 38 | @Test 39 | public void checkMessageFromJSONObjectFailedCompare_negative() { 40 | String expected = "{\"a\":true,\"b\":0}"; 41 | String actual = "{\"a\":true,\"b\":0}"; 42 | try { 43 | JSONCompare.assertNotMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE)), "JSONs do match"); 44 | } catch (AssertionError e) { 45 | assertTrue(e.getMessage().contains("JSONs do match") && e.getMessage().contains("JSONs are equal")); 46 | } 47 | } 48 | 49 | @Test 50 | public void checkMessageFromJSONObjectFailedCompare_negative1() { 51 | String expected = "{\"a\":true,\"b\":0}"; 52 | String actual = "{\"a\":true,\"b\":0}"; 53 | try { 54 | JSONCompare.assertNotMatches(expected, actual, null, new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE)), "JSONs do match"); 55 | } catch (AssertionError e) { 56 | assertTrue(e.getMessage().contains("JSONs do match") && e.getMessage().contains("JSONs are equal")); 57 | } 58 | } 59 | 60 | @Test 61 | public void checkDefaultMessageFromJSONsWhichDoNotMatch() { 62 | String expected = "{\"a\":true}"; 63 | String actual = "{\"a\":true}"; 64 | try { 65 | JSONCompare.assertNotMatches(expected, actual); 66 | } catch (AssertionError e) { 67 | assertTrue(e.getMessage().contains("JSONs are equal")); 68 | } 69 | } 70 | 71 | @Test 72 | public void checkNullMessageFromJSONObjectFailedCompare() { 73 | String expected = "{\"a\":true}"; 74 | String actual = "{\"ab\":true}"; 75 | try { 76 | JSONCompare.assertMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE))); 77 | } catch (AssertionError e) { 78 | assertTrue(e.getMessage().contains("$.a was not found")); 79 | } 80 | } 81 | 82 | @Test 83 | public void checkMessageFromFailedMatchingBetweenHighDepthJsons() { 84 | String expected = "{\n" + 85 | " \"@\": {\n" + 86 | " \"instanceId2\": {\n" + 87 | " \"!endDate\": \".*\",\n" + 88 | " \"groupIds\": [\n" + 89 | " \"gr1\",\n" + 90 | " \"gr2\"\n" + 91 | " ]\n" + 92 | " },\n" + 93 | " \"version\": 0,\n" + 94 | " \"!.*\": \".*\"\n" + 95 | " }}"; 96 | String actual = "{\n" + 97 | " \"@\" : {\n" + 98 | " \"instanceId2\" : {\n" + 99 | " \"version\" : 0,\n" + 100 | " \"groupIds\" : [ \"gr2\", \"gr1\" ]\n" + 101 | " }\n" + 102 | " }}"; 103 | 104 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual, 105 | new HashSet<>(Collections.singletonList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE)))); 106 | assertTrue(error.getMessage().matches("(?s).*FOUND 2 DIFFERENCE.*" + 107 | "\\Q$.@.instanceId2\\E -> Actual JSON OBJECT has extra fields.*" + 108 | "\\Q$.@.version\\E was not found.*")); 109 | JSONCompare.assertNotMatches(expected, actual, 110 | new HashSet<>(Collections.singletonList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE))); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONObjectCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class JSONObjectCompareTests { 7 | 8 | @Test 9 | public void compareSimple() { 10 | String expected = "{\"b\":\"\\\\Q(⌐■_■)\\\\E\",\"a\":\"val2\"}"; 11 | String actual = "{\"a\":\"val2\",\"b\":\"(⌐■_■)\"}"; 12 | JSONCompare.assertMatches(expected, actual); 13 | } 14 | 15 | @Test 16 | public void compareEmptyJsonObject() { 17 | String expected = "{}"; 18 | String actual = "{\"a\":\"val2\"}"; 19 | JSONCompare.assertMatches(expected, actual); 20 | } 21 | 22 | @Test 23 | public void compareEmptyJsonObject_negative() { 24 | String expected = "{}"; 25 | String actual = "[1,2]"; 26 | JSONCompare.assertNotMatches(expected, actual); 27 | } 28 | 29 | @Test 30 | public void compareByFieldNotFound() { 31 | String expected = "{\"!b\":\"val1\",\"a\":\"val2\"}"; 32 | String actual = "{\"a\":\"val2\",\"c\":\"val1\"}"; 33 | JSONCompare.assertMatches(expected, actual); 34 | } 35 | 36 | @Test 37 | public void compareByFieldNotFound_Negative() { 38 | String expected = "{\"!b\":\"val1\",\"a\":\"val2\"}"; 39 | String actual = "{\"a\":\"val2\",\"b\":\"val1\"}"; 40 | JSONCompare.assertNotMatches(expected, actual); 41 | } 42 | 43 | @Test 44 | public void compareWithNumbers() { 45 | String expected = "{\"b\":1,\"a\":2}"; 46 | String actual = "{\"a\":2,\"b\":1}"; 47 | JSONCompare.assertMatches(expected, actual); 48 | } 49 | 50 | @Test 51 | public void compareWithExpectedStringRepresentedNumbers() { 52 | String expected = "{\"b\":1,\"a\":\"2\"}"; 53 | String actual = "{\"a\":2,\"b\":1}"; 54 | JSONCompare.assertMatches(expected, actual); 55 | } 56 | 57 | @Test 58 | public void compareWithFloatNumbers() { 59 | String expected = "{\"b\":-10.54325,\"a\":10.429318549148632}"; 60 | String actual = "{\"a\":10.429318549148632,\"b\":-10.54325}"; 61 | JSONCompare.assertMatches(expected, actual); 62 | } 63 | 64 | @Test 65 | public void compareWithFloatNumbers_negative() { 66 | String expected = "{\"b\":-10.54325,\"a\":10.429318549148632}"; 67 | String actual = "{\"a\":10.429318549148632,\"b\":-10.5432}"; 68 | JSONCompare.assertNotMatches(expected, actual); 69 | } 70 | 71 | @Test 72 | public void compareWithSpecialCharacters() { 73 | String expected = "{\"b\":\"\\\\Qso!@!$#@%$#^&^%*)(*&\\\\Eme \\n\\t te\\\\\\\\'xt\"}"; 74 | String actual = "{\"b\":\"so!@!$#@%$#^&^%*)(*&me \\n\\t te\\\\'xt\"}"; 75 | JSONCompare.assertMatches(expected, actual); 76 | } 77 | 78 | @Test 79 | public void compareWithSpecialCharacters_negative() { 80 | String expected = "{\"b\":\"some \\n\\t text\"}"; 81 | String actual = "{\"b\":\"some \\\\n\\\\t text\"}"; 82 | JSONCompare.assertNotMatches(expected, actual); 83 | } 84 | 85 | @Test 86 | public void compareWithActualStringRepresentedNumbers_negative() { 87 | String expected = "{\"b\":1,\"a\":2}"; 88 | String actual = "{\"a\":\"2\",\"b\":1}"; 89 | JSONCompare.assertNotMatches(expected, actual); 90 | } 91 | 92 | @Test 93 | public void deepCompareJson() { 94 | String expected = 95 | "{\"b\":{\"x\":\"val1\",\"y\":\"val2\"},\"a\":{\"t\":\"val3\",\"z\":\"val4\"}}"; 96 | String actual = 97 | "{\"a\":{\"t\":\"val3\",\"z\":\"val4\"},\"b\":{\"x\":\"val1\",\"y\":\"val2\"}}"; 98 | JSONCompare.assertMatches(expected, actual); 99 | } 100 | 101 | @Test 102 | public void deepCompareJson_negative() { 103 | String expected = 104 | "{\"b\":{\"x\":\"val1\",\"y\":\"val2\"},\"a\":{\"t\":\"val3\",\"z\":\"val3\"}}"; 105 | String actual = 106 | "{\"a\":{\"t\":\"val3\",\"z\":\"notval3\"},\"b\":{\"x\":\"val1\",\"y\":\"val2\"}}"; 107 | JSONCompare.assertNotMatches(expected, actual); 108 | } 109 | 110 | @Test 111 | public void compareForExtraFields() { 112 | String expected = "{\"b\":\"val1\",\"a\":\"val2\",\".*\":\".*\"}"; 113 | String actual = "{\"a\":\"val2\",\"b\":\"val1\",\"c\":\"val3\",\"d\":\"val4\"}"; 114 | JSONCompare.assertMatches(expected, actual); 115 | } 116 | 117 | @Test 118 | public void compareForExtraFields1() { 119 | String expected = "{\"b\":\"val1\",\"a\":\"val2\",\".*\":\".*\"}"; 120 | String actual = "{\"a\":\"val2\",\"b\":\"val1\",\"c\":{\"a\":0}}"; 121 | JSONCompare.assertMatches(expected, actual); 122 | } 123 | 124 | @Test 125 | public void compareForExtraFields_negative() { 126 | String expected = "{\"b\":\"val1\",\"a\":\"val2\",\".*\":\".*\"}"; 127 | String actual = "{\"a\":\"val2\",\"b\":\"val1\"}"; 128 | JSONCompare.assertNotMatches(expected, actual); 129 | } 130 | 131 | @Test 132 | public void compareForNoExtraFields() { 133 | String expected = "{\"b\":\"val1\",\"a\":\"val2\",\"!.*\":\".*\"}"; 134 | String actual = "{\"a\":\"val2\",\"b\":\"val1\"}"; 135 | JSONCompare.assertMatches(expected, actual); 136 | } 137 | 138 | @Test 139 | public void compareForNoExtraFields_negative() { 140 | String expected = "{\"b\":\"val1\",\"a\":\"val2\",\"!.*\":\".*\"}"; 141 | String actual = "{\"a\":\"val2\",\"b\":\"val1\",\"c\":\"val3\"}"; 142 | JSONCompare.assertNotMatches(expected, actual); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue1Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.JSONCompare; 4 | import io.json.compare.JsonComparator; 5 | import org.junit.jupiter.api.Test; 6 | 7 | /** 8 | * https://github.com/fslev/json-compare/issues/1 9 | */ 10 | public class Issue1Test { 11 | 12 | @Test 13 | public void testIssue() { 14 | String expected = 15 | "{\"field\":\"Moss Agate Cluster Bracelet strengthens in times of stress. It releases fears as it is made from genuine moss agate crystals. It is helpful in relieving sensitivities to weather and pollution. This can be used to speed up recovery, for a long term illnesses, cleanse the circulatory and elimination systems. It enhances concentration and persistence. It is an abundance stone. This affordable bracelet is great for daily wear to give you the benefit of the stone's energy all day long.\\n\\n1. Wear this for: supports relaxation, regeneration and recovery\\n2. promotes intuitive action leading to a cleansing of consciousness\\n3. sets you free from the walls of emotional limitation and helps you achieve independence\\n \\\\Q(Rest Details and Usage Method will be sent along with the product)\\\\E\"}"; 16 | String actual = 17 | "{\"field\":\"Moss Agate Cluster Bracelet strengthens in times of stress. It releases fears as it is made from genuine moss agate crystals. It is helpful in relieving sensitivities to weather and pollution. This can be used to speed up recovery, for a long term illnesses, cleanse the circulatory and elimination systems. It enhances concentration and persistence. It is an abundance stone. This affordable bracelet is great for daily wear to give you the benefit of the stone's energy all day long.\\n\\n1. Wear this for: supports relaxation, regeneration and recovery\\n2. promotes intuitive action leading to a cleansing of consciousness\\n3. sets you free from the walls of emotional limitation and helps you achieve independence\\n (Rest Details and Usage Method will be sent along with the product)\"}"; 18 | JSONCompare.assertMatches(expected, actual); 19 | } 20 | 21 | @Test 22 | public void testIssue_negative() { 23 | String expected = 24 | "{\"field\":\"Moss Agate Cluster Bracelet strengthens in times of stress. It releases fears as it is made from genuine moss agate crystals. It is helpful in relieving sensitivities to weather and pollution. This can be used to speed up recovery, for a long term illnesses, cleanse the circulatory and elimination systems. It enhances concentration and persistence. It is an abundance stone. This affordable bracelet is great for daily wear to give you the benefit of the stone's energy all day long.\\n\\n1. Wear this for: supports relaxation, regeneration and recovery\\n2. promotes intuitive action leading to a cleansing of consciousness\\n3. sets you free from the walls of emotional limitation and helps you achieve independence\\n (Rest Details and Usage Method will be sent along with the product)\"}"; 25 | String actual = 26 | "{\"field\":\"Moss Agate Cluster Bracelet strengthens in times of stress. It releases fears as it is made from genuine moss agate crystals. It is helpful in relieving sensitivities to weather and pollution. This can be used to speed up recovery, for a long term illnesses, cleanse the circulatory and elimination systems. It enhances concentration and persistence. It is an abundance stone. This affordable bracelet is great for daily wear to give you the benefit of the stone's energy all day long.\\n\\n1. Wear this for: supports relaxation, regeneration and recovery\\n2. promotes intuitive action leading to a cleansing of consciousness\\n3. sets you free from the walls of emotional limitation and helps you achieve independence\\n (Rest Details and Usage Method will be sent along with the product)\"}"; 27 | JSONCompare.assertNotMatches(expected, actual); 28 | } 29 | 30 | @Test 31 | public void isolateIssue() { 32 | String expected = "{\"field\":\"new li'ne.\\n\\n1. \\\\Q(wa)\\\\E\"}"; 33 | String actual = "{\"field\":\"new li'ne.\\n\\n1. (wa)\"}"; 34 | JSONCompare.assertMatches(expected, actual); 35 | } 36 | 37 | @Test 38 | public void isolateIssue_negative() { 39 | String expected = "{\"field\":\"new line.\\n\\n1. (wa)\"}"; 40 | String actual = "{\"field\":\"new line.\\n\\n1. (wa)\"}"; 41 | JSONCompare.assertNotMatches(expected, actual); 42 | } 43 | 44 | @Test 45 | public void isolateIssueWithInvalidRegexPattern() { 46 | String expected = "{\"field\":\"new line.\\n\\n1. (wa\"}"; 47 | String actual = "{\"field\":\"new line.\\n\\n1. (wa\"}"; 48 | JSONCompare.assertMatches(expected, actual); 49 | } 50 | 51 | @Test 52 | public void isolateIssueWithInvalidRegexpattern_negative() { 53 | String expected = "{\"field\":\"Snew line.\\n\\n1. (wa\"}"; 54 | String actual = "{\"field\":\"new line.\\n\\n1. (wa\"}"; 55 | JSONCompare.assertNotMatches(expected, actual); 56 | } 57 | 58 | @Test 59 | public void doNotUseRegexComparator() { 60 | String expected = "{\"field\":\"new li'ne.\\n\\n1. (wa)\"}"; 61 | String actual = "{\"field\":\"new li'ne.\\n\\n1. (wa)\"}"; 62 | JSONCompare.assertMatches(expected, actual, new JsonComparator() { 63 | public boolean compareValues(Object expected, Object actual) { 64 | return expected.toString().toLowerCase().equals(actual.toString().toLowerCase()); 65 | } 66 | 67 | public boolean compareFields(String expected, String actual) { 68 | return expected.toLowerCase().equals(actual.toLowerCase()); 69 | } 70 | }); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/matcher/AbstractJsonMatcher.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import io.json.compare.CompareMode; 5 | import io.json.compare.DefaultJsonComparator; 6 | import io.json.compare.JsonComparator; 7 | 8 | import java.util.HashSet; 9 | import java.util.Iterator; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.Set; 13 | 14 | abstract class AbstractJsonMatcher { 15 | 16 | protected static final String JSON_PATH_EXP_PREFIX = "#("; 17 | protected static final String JSON_PATH_EXP_SUFFIX = ")"; 18 | 19 | protected final JsonComparator comparator; 20 | protected final Set compareModes; 21 | protected final JsonNode expected; 22 | protected final JsonNode actual; 23 | 24 | AbstractJsonMatcher(JsonNode expected, JsonNode actual, JsonComparator comparator, Set compareModes) { 25 | this.expected = expected; 26 | this.actual = actual; 27 | this.compareModes = compareModes == null ? new HashSet<>() : compareModes; 28 | this.comparator = comparator == null ? new DefaultJsonComparator(this.compareModes) : comparator; 29 | } 30 | 31 | protected abstract List match(); 32 | 33 | protected static UseCase getUseCase(JsonNode node) { 34 | if (node.isTextual()) { 35 | return getUseCase(node.asText()); 36 | } 37 | return UseCase.MATCH; 38 | } 39 | 40 | protected static UseCase getUseCase(String value) { 41 | if (value.equals(UseCase.MATCH_ANY.getValue())) { 42 | return UseCase.MATCH_ANY; 43 | } else if (value.equals(UseCase.DO_NOT_MATCH_ANY.getValue())) { 44 | return UseCase.DO_NOT_MATCH_ANY; 45 | } else if (value.startsWith(UseCase.DO_NOT_MATCH.getValue())) { 46 | return UseCase.DO_NOT_MATCH; 47 | } else return UseCase.MATCH; 48 | } 49 | 50 | protected static String sanitize(String value) { 51 | if (getUseCase(value) == UseCase.DO_NOT_MATCH || getUseCase(value) == UseCase.DO_NOT_MATCH_ANY) { 52 | return value.substring(1); 53 | } 54 | return removeEscapes(value); 55 | } 56 | 57 | protected static Optional extractJsonPathExp(String field) { 58 | if (field.startsWith(JSON_PATH_EXP_PREFIX) && field.endsWith(JSON_PATH_EXP_SUFFIX)) { 59 | return Optional.of(field.substring(JSON_PATH_EXP_PREFIX.length(), field.length() - JSON_PATH_EXP_SUFFIX.length())); 60 | } 61 | return Optional.empty(); 62 | } 63 | 64 | protected static int getDoNotMatchUseCases(JsonNode jsonNode) { 65 | int count = 0; 66 | if (jsonNode.isArray()) { 67 | for (int i = 0; i < jsonNode.size(); i++) { 68 | if (getUseCase(jsonNode.get(i)).equals(UseCase.DO_NOT_MATCH_ANY) || 69 | getUseCase(jsonNode.get(i)).equals(UseCase.DO_NOT_MATCH) || isJsonPathNode(jsonNode.get(i))) { 70 | count++; 71 | } 72 | } 73 | } else if (jsonNode.isObject()) { 74 | Iterator it = jsonNode.fieldNames(); 75 | while (it.hasNext()) { 76 | String field = it.next(); 77 | if (getUseCase(field).equals(UseCase.DO_NOT_MATCH_ANY) || getUseCase(field).equals(UseCase.DO_NOT_MATCH) 78 | || extractJsonPathExp(field).isPresent()) { 79 | count++; 80 | } 81 | } 82 | } 83 | return count; 84 | } 85 | 86 | private static String removeEscapes(String value) { 87 | if (value == null) { 88 | return null; 89 | } 90 | if (value.startsWith("\\" + UseCase.DO_NOT_MATCH.getValue()) || 91 | value.equals("\\" + UseCase.DO_NOT_MATCH_ANY.getValue()) || 92 | value.equals("\\" + UseCase.MATCH_ANY.getValue()) || 93 | value.startsWith("\\" + JSON_PATH_EXP_PREFIX)) { 94 | return value.replaceFirst("\\\\", ""); 95 | } 96 | return value; 97 | } 98 | 99 | protected static boolean isJsonObject(JsonNode jsonNode) { 100 | return jsonNode != null && jsonNode.isObject(); 101 | } 102 | 103 | protected static boolean isJsonArray(JsonNode jsonNode) { 104 | return jsonNode != null && jsonNode.isArray(); 105 | } 106 | 107 | protected static boolean isValueNode(JsonNode jsonNode) { 108 | return jsonNode != null && jsonNode.isValueNode(); 109 | } 110 | 111 | protected static boolean isJsonPathNode(JsonNode jsonNode) { 112 | if (jsonNode != null && jsonNode.isObject()) { 113 | Iterator fieldNames = jsonNode.fieldNames(); 114 | if (fieldNames.hasNext()) { 115 | while (fieldNames.hasNext()) { 116 | if (!extractJsonPathExp(fieldNames.next()).isPresent()) { 117 | return false; 118 | } 119 | } 120 | return true; 121 | } 122 | } 123 | return false; 124 | } 125 | 126 | protected static boolean isMissingNode(JsonNode jsonNode) { 127 | return jsonNode != null && jsonNode.isMissingNode(); 128 | } 129 | 130 | protected static boolean areOfSameType(JsonNode expNode, JsonNode actNode) { 131 | return (isValueNode(expNode) & isValueNode(actNode)) || (isJsonObject(expNode) & isJsonObject(actNode)) 132 | || (isJsonArray(expNode) & isJsonArray(actNode) || isJsonPathNode(expNode)); 133 | } 134 | 135 | protected enum UseCase { 136 | MATCH, DO_NOT_MATCH("!"), MATCH_ANY(".*"), DO_NOT_MATCH_ANY("!.*"); 137 | private String value; 138 | 139 | UseCase() { 140 | } 141 | 142 | UseCase(String value) { 143 | this.value = value; 144 | } 145 | 146 | public String getValue() { 147 | return value; 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/matcher/JsonObjectMatcher.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.jayway.jsonpath.PathNotFoundException; 5 | import io.json.compare.CompareMode; 6 | import io.json.compare.JsonComparator; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.HashSet; 11 | import java.util.Iterator; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Optional; 15 | import java.util.Set; 16 | 17 | class JsonObjectMatcher extends AbstractJsonMatcher { 18 | 19 | private final Set matchedFieldNames = new HashSet<>(); 20 | 21 | JsonObjectMatcher(JsonNode expected, JsonNode actual, JsonComparator comparator, Set compareModes) { 22 | super(expected, actual, comparator, compareModes); 23 | } 24 | 25 | @Override 26 | public List match() { 27 | List diffs = new ArrayList<>(); 28 | 29 | Iterator> it = expected.fields(); 30 | while (it.hasNext()) { 31 | Map.Entry entry = it.next(); 32 | String expectedField = entry.getKey(); 33 | JsonNode expectedValue = entry.getValue(); 34 | UseCase fieldUseCase = getUseCase(expectedField); 35 | String expectedSanitizedField = sanitize(expectedField); 36 | Optional jsonPathExpression = extractJsonPathExp(expectedSanitizedField); 37 | List> candidateEntries = null; 38 | if (!jsonPathExpression.isPresent()) { 39 | candidateEntries = searchCandidatesByField(fieldUseCase, expectedSanitizedField, actual); 40 | } 41 | switch (fieldUseCase) { 42 | case MATCH_ANY: 43 | case MATCH: 44 | if (!jsonPathExpression.isPresent()) { 45 | if (candidateEntries.isEmpty()) { 46 | diffs.add(String.format(".%s was not found", expectedField)); 47 | } else { 48 | diffs.addAll(matchWithCandidates(expectedSanitizedField, expectedValue, candidateEntries)); 49 | } 50 | } else { 51 | try { 52 | diffs.addAll(new JsonPathMatcher(jsonPathExpression.get(), expectedValue, actual, comparator, compareModes).match()); 53 | } catch (PathNotFoundException e) { 54 | diffs.add(String.format("." + JSON_PATH_EXP_PREFIX + "%s" + JSON_PATH_EXP_SUFFIX + " -> Json path -> %s", jsonPathExpression.get(), e.getMessage())); 55 | } 56 | } 57 | break; 58 | case DO_NOT_MATCH_ANY: 59 | if (expected.size() - getDoNotMatchUseCases(expected) < actual.size()) { 60 | diffs.add(String.format(".\"%s\" condition was not met. Actual JSON OBJECT has extra fields", expectedField)); 61 | } 62 | break; 63 | case DO_NOT_MATCH: 64 | if (!jsonPathExpression.isPresent()) { 65 | if (!candidateEntries.isEmpty()) { 66 | diffs.add(String.format(".\"%s\" was found", expectedField)); 67 | } 68 | } else { 69 | try { 70 | new JsonPathMatcher(jsonPathExpression.get(), expectedValue, actual, comparator, compareModes).match(); 71 | } catch (PathNotFoundException e) { 72 | break; 73 | } 74 | diffs.add(String.format("." + JSON_PATH_EXP_PREFIX + "%s" + JSON_PATH_EXP_SUFFIX + " -> Json path was found", expectedField)); 75 | } 76 | break; 77 | } 78 | } 79 | if (compareModes.contains(CompareMode.JSON_OBJECT_NON_EXTENSIBLE) && expected.size() - getDoNotMatchUseCases(expected) < actual.size()) { 80 | diffs.add(" -> Actual JSON OBJECT has extra fields"); 81 | } 82 | return diffs; 83 | } 84 | 85 | private List matchWithCandidates(String expectedField, JsonNode expectedValue, List> candidates) { 86 | List diffs = new ArrayList<>(); 87 | 88 | UseCase expectedValueUseCase = getUseCase(expectedValue); 89 | 90 | for (Map.Entry candidateEntry : candidates) { 91 | String candidateField = candidateEntry.getKey(); 92 | 93 | if (expectedValueUseCase == UseCase.MATCH_ANY) { 94 | matchedFieldNames.add(candidateField); 95 | return Collections.emptyList(); 96 | } 97 | 98 | JsonNode candidateValue = candidateEntry.getValue(); 99 | List candidateDiffs = new JsonMatcher(expectedValue, candidateValue, comparator, compareModes).match(); 100 | if (candidateDiffs.isEmpty()) { 101 | matchedFieldNames.add(candidateField); 102 | return Collections.emptyList(); 103 | } else { 104 | candidateDiffs.forEach(diff -> diffs.add(String.format(".%s%s", expectedField, diff))); 105 | } 106 | } 107 | return diffs; 108 | } 109 | 110 | private List> searchCandidatesByField(UseCase fieldUseCase, String fieldName, JsonNode target) { 111 | List> candidates = new ArrayList<>(); 112 | Iterator> it = target.fields(); 113 | while (it.hasNext()) { 114 | Map.Entry entry = it.next(); 115 | String key = entry.getKey(); 116 | if (matchedFieldNames.contains(key)) { 117 | continue; 118 | } 119 | if (fieldUseCase.equals(UseCase.MATCH_ANY) || comparator.compareFields(fieldName, key)) { 120 | candidates.add(entry); 121 | } 122 | } 123 | return candidates; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/diffs/JsonPathDiffTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.diffs; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | class JsonPathDiffTests { 10 | 11 | @Test 12 | void checkMultipleJsonPathDifferences() { 13 | String expected = "{\"#($.a.length())\":3,\"b\":\"val1\",\"x\":{\"x1\":{\"y11\":{\"#($.www)\":\"lorem1\"}}},\"z\":[{\"#($.length())\":1}]," + 14 | "\"u\":{\"#($.u1)\":{\"u11\":20209}}}"; 15 | String actual = "{\"z\":[2,3,4],\"x\":{\"x2\":290.11,\"x1\":{\"x11\":null,\"y11\":{\"a\":\"lorem2\"}}},\"b\":\"val2\",\"a\":[4,5]," + 16 | "\"u\":{\"u1\":{\"u11\":20000}}}"; 17 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 18 | assertTrue(error.getMessage().matches("(?s).*FOUND 5 DIFFERENCE.*" + 19 | "\\Q$.#($.a.length())\\E.*Expected json path result.*" + 20 | "3.*But got.*2.*" + 21 | "\\Q$.b\\E.*Expected value: \"val1\" But got: \"val2\".*" + 22 | "\\Q$.x.x1.y11.#($.www)\\E.*No results for path.*" + 23 | "\\Q$.z.#($.length())\\E.*Expected json path result.*" + 24 | "1.*But got.*3.*" + 25 | "\\Qu.#($.u1).u11\\E.*Expected value: 20209 But got: 20000.*Expected json path result.*")); 26 | JSONCompare.assertNotMatches(expected, actual); 27 | 28 | String expected1 = "{\"#($.a.length())\":2,\"b\":\"val2\",\"x\":{\"x1\":{\"y11\":{\"#($.a)\":\"lorem2\"}}}," + 29 | "\"u\":{\"#($.u1)\":{\"u11\":20000}}}"; 30 | String actual1 = "{\"x\":{\"x2\":290.11,\"x1\":{\"x11\":null,\"y11\":{\"a\":\"lorem2\"}}},\"b\":\"val2\",\"a\":[4,5]," + 31 | "\"u\":{\"u1\":{\"u11\":20000}}}"; 32 | JSONCompare.assertMatches(expected1, actual1); 33 | } 34 | 35 | @Test 36 | void checkMultipleJsonPathDifferencesFromArray() { 37 | String expected = "[false, {\"#($.length())\":1}, \"b\",{\"x\":{\"#($.length())\":2}}]"; 38 | String actual = "[\"b\",false,{\"x\":[1,2,5]}, {\"w\":\"yyyy\"}]"; 39 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 40 | assertTrue(error.getMessage().matches("(?s).*FOUND 2 DIFFERENCE.*" + 41 | "\\Q$.#($.length())\\E.*Expected json path result.*" + 42 | "1.*But got.*4.*" + 43 | "\\Q$[3]\\E was not found:.*")); 44 | JSONCompare.assertNotMatches(expected, actual); 45 | 46 | String expected1 = "[false, {\"#($.length())\":1}, \"b\",{\"x\":{\"#($.length())\":3}}]"; 47 | String actual1 = "[\"b\",false,{\"x\":[1,2,5]}, {\"w\":\"yyyy\"}]"; 48 | AssertionError error1 = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected1, actual1)); 49 | assertTrue(error1.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 50 | "\\Q$.#($.length())\\E.*Expected json path result.*" + 51 | "1.*But got.*4.*")); 52 | JSONCompare.assertNotMatches(expected1, actual1); 53 | 54 | String expected2 = "[false, {\"#($.length())\":4}, \"b\",{\"x\":{\"#($.length())\":3}}]"; 55 | String actual2 = "[\"b\",false,{\"x\":[1,2,5]}, {\"w\":\"yyyy\"}]"; 56 | JSONCompare.assertMatches(expected2, actual2); 57 | } 58 | 59 | @Test 60 | void checkDoNotMatchAnyAndJsonPathsFromArray() { 61 | String expected = "[false, {\"#($.length())\":1}, \"b\",{\"x\":{\"#($.length())\":2}}, \"!.*\"]"; 62 | String actual = "[\"b\",false,{\"x\":[1,2,5]}, {\"w\":\"yyyy\"}]"; 63 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 64 | assertTrue(error.getMessage().matches("(?s).*FOUND 3 DIFFERENCE.*" + 65 | "\\Q$.#($.length())\\E.*" + 66 | "1.*But got.*4.*" + 67 | "\\Q$[3]\\E was not found:.*" + 68 | "\\Q$[4]\\E -> Expected condition \"!.*\" was not met. Actual JSON ARRAY has extra elements.*")); 69 | JSONCompare.assertNotMatches(expected, actual); 70 | 71 | String expected1 = "[false, {\"#($.length())\":3}, \"b\",{\"x\":{\"#($.length())\":3}}, \"!.*\"]"; 72 | String actual1 = "[\"b\",false,{\"x\":[1,2,5]}]"; 73 | JSONCompare.assertMatches(expected1, actual1); 74 | } 75 | 76 | @Test 77 | void checkDoNotMatchAnyAndJsonPathsFromObject() { 78 | String expected = "{\"#($.length())\":2, \"b\":\"val1\", \"!.*\":\".*\"}"; 79 | String actual = "{\"b\":\"val1\", \"a\":\"val2\"}"; 80 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 81 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 82 | "\\Q$.\"!.*\"\\E condition was not met. Actual JSON OBJECT has extra fields.*")); 83 | JSONCompare.assertNotMatches(expected, actual); 84 | 85 | 86 | String expected1 = "{\"#($.length())\":2, \"b\":\"val1\", \"!name\":\".*\", \"!.*\":\".*\"}"; 87 | String actual1 = "{\"b\":\"val1\", \"a\":\"val2\"}"; 88 | error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 89 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 90 | "\\Q$.\"!.*\"\\E condition was not met. Actual JSON OBJECT has extra fields.*")); 91 | JSONCompare.assertNotMatches(expected1, actual1); 92 | 93 | expected1 = "{\"#($.length())\":1, \"b\":\"val1\", \"!name\":\".*\", \"!.*\":\".*\"}"; 94 | actual1 = "{\"b\":\"val1\"}"; 95 | JSONCompare.assertMatches(expected1, actual1); 96 | 97 | expected1 = "{\"#($.length())\":2, \"a\":\"val2\", \"b\":\"val1\", \"!name\":\".*\", \"!.*\":\".*\"}"; 98 | actual1 = "{\"b\":\"val1\", \"a\":\"val2\"}"; 99 | JSONCompare.assertMatches(expected1, actual1); 100 | 101 | expected1 = "{\"#($.length())\":0, \"!name\":\".*\", \"!.*\":\".*\"}"; 102 | actual1 = "{}"; 103 | JSONCompare.assertMatches(expected1, actual1); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/io/json/compare/JSONCompare.java: -------------------------------------------------------------------------------- 1 | package io.json.compare; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import io.json.compare.matcher.JsonMatcher; 5 | import io.json.compare.util.JsonUtils; 6 | import org.junit.jupiter.api.AssertionFailureBuilder; 7 | 8 | import java.io.IOException; 9 | import java.util.List; 10 | import java.util.Set; 11 | import java.util.stream.Collectors; 12 | 13 | 14 | /** 15 | * @author fslev 16 | */ 17 | 18 | public class JSONCompare { 19 | 20 | private JSONCompare() { 21 | } 22 | 23 | private static final String ASSERTION_ERROR_HINT_MESSAGE = "Json matching is by default case-sensitive and uses regular expressions." + System.lineSeparator() + 24 | "In case expected json contains any unintentional regexes, then quote them between \\Q and \\E delimiters.\n" + 25 | "For disabling case-sensitivity, use (?i) and (?-i) modifiers. Or, use a custom comparator."; 26 | 27 | public static void assertMatches(Object expected, Object actual) { 28 | assertMatches(expected, actual, null, null, null); 29 | } 30 | 31 | public static void assertNotMatches(Object expected, Object actual) { 32 | assertNotMatches(expected, actual, null, null, null); 33 | } 34 | 35 | public static void assertMatches(Object expected, Object actual, Set compareModes) { 36 | assertMatches(expected, actual, null, compareModes); 37 | } 38 | 39 | public static void assertNotMatches(Object expected, Object actual, Set compareModes) { 40 | assertNotMatches(expected, actual, null, compareModes); 41 | } 42 | 43 | public static void assertMatches(Object expected, Object actual, JsonComparator comparator) { 44 | assertMatches(expected, actual, comparator, null); 45 | } 46 | 47 | public static void assertNotMatches(Object expected, Object actual, JsonComparator comparator) { 48 | assertNotMatches(expected, actual, comparator, null); 49 | } 50 | 51 | public static void assertMatches(Object expected, Object actual, JsonComparator comparator, Set compareModes) { 52 | assertMatches(expected, actual, comparator, compareModes, null); 53 | } 54 | 55 | public static void assertNotMatches(Object expected, Object actual, JsonComparator comparator, Set compareModes) { 56 | assertNotMatches(expected, actual, comparator, compareModes, null); 57 | } 58 | 59 | public static void assertMatches(Object expected, Object actual, Set compareModes, String message) { 60 | assertMatches(expected, actual, null, compareModes, message); 61 | } 62 | 63 | public static void assertNotMatches(Object expected, Object actual, Set compareModes, String message) { 64 | assertNotMatches(expected, actual, null, compareModes, message); 65 | } 66 | 67 | public static void assertMatches(Object expected, Object actual, JsonComparator comparator, Set compareModes, String message) { 68 | JsonNode expectedJson = toJson(expected); 69 | JsonNode actualJson = toJson(actual); 70 | List diffs = new JsonMatcher(expectedJson, actualJson, 71 | comparator == null ? new DefaultJsonComparator(compareModes) : comparator, compareModes).match(); 72 | if (!diffs.isEmpty()) { 73 | String defaultMessage = String.format("FOUND %s DIFFERENCE(S):%s%s%s", 74 | diffs.size(), System.lineSeparator(), diffs.stream().map(diff -> 75 | System.lineSeparator() + System.lineSeparator() + "_________________________DIFF__________________________" + 76 | System.lineSeparator() + "$" + diff).reduce(String::concat).get(), System.lineSeparator()); 77 | if (comparator == null || comparator.getClass().equals(DefaultJsonComparator.class)) { 78 | defaultMessage += System.lineSeparator() + System.lineSeparator() + ASSERTION_ERROR_HINT_MESSAGE + System.lineSeparator(); 79 | } 80 | AssertionFailureBuilder.assertionFailure().message(message == null ? defaultMessage : defaultMessage + System.lineSeparator() + message) 81 | .expected(prettyPrint(expectedJson)).actual(prettyPrint(actualJson)).buildAndThrow(); 82 | } 83 | } 84 | 85 | public static void assertNotMatches(Object expected, Object actual, JsonComparator comparator, Set compareModes, String message) { 86 | JsonNode expectedJson = toJson(expected); 87 | JsonNode actualJson = toJson(actual); 88 | List diffs = new JsonMatcher(expectedJson, actualJson, 89 | comparator == null ? new DefaultJsonComparator(compareModes) : comparator, compareModes).match(); 90 | if (!diffs.isEmpty()) { 91 | return; 92 | } 93 | String defaultMessage = System.lineSeparator() + "JSONs are equal"; 94 | AssertionFailureBuilder.assertionFailure().message(message == null ? defaultMessage : defaultMessage + System.lineSeparator() + message) 95 | .expected(prettyPrint(expectedJson)).actual(prettyPrint(actualJson)) 96 | .includeValuesInMessage(false).buildAndThrow(); 97 | } 98 | 99 | public static List diffs(Object expected, Object actual) { 100 | return diffs(expected, actual, null, null); 101 | } 102 | 103 | public static List diffs(Object expected, Object actual, Set compareModes) { 104 | return diffs(expected, actual, null, compareModes); 105 | } 106 | 107 | public static List diffs(Object expected, Object actual, JsonComparator comparator) { 108 | return diffs(expected, actual, comparator, null); 109 | } 110 | 111 | public static List diffs(Object expected, Object actual, JsonComparator comparator, Set compareModes) { 112 | JsonNode expectedJson = toJson(expected); 113 | JsonNode actualJson = toJson(actual); 114 | List diffs = new JsonMatcher(expectedJson, actualJson, 115 | comparator == null ? new DefaultJsonComparator(compareModes) : comparator, compareModes).match(); 116 | return diffs.stream().map(diff -> "$" + diff).collect(Collectors.toList()); 117 | } 118 | 119 | public static String prettyPrint(JsonNode jsonNode) { 120 | try { 121 | return JsonUtils.prettyPrint(jsonNode); 122 | } catch (IOException e) { 123 | throw new RuntimeException(e); 124 | } 125 | } 126 | 127 | private static JsonNode toJson(Object obj) { 128 | try { 129 | return JsonUtils.toJson(obj); 130 | } catch (IOException e) { 131 | throw new RuntimeException(String.format("Invalid JSON%s%s%s", System.lineSeparator(), e, System.lineSeparator())); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONMixedCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class JSONMixedCompareTests { 7 | 8 | @Test 9 | public void compareSimple() { 10 | String expected = "" + 11 | "{\n" + 12 | " \"a\": \"val1\",\n" + 13 | " \"b\": \"val2\",\n" + 14 | " \"c\": [\n" + 15 | " 1,\n" + 16 | " null,\n" + 17 | " true,\n" + 18 | " \"text\",\n" + 19 | " {\n" + 20 | " \"c1\": \"some text\"\n" + 21 | " }\n" + 22 | " ],\n" + 23 | " \"d\": true,\n" + 24 | " \"e\": 1024,\n" + 25 | " \"f\": null,\n" + 26 | " \"g\": \"\"\n" + 27 | "}" + 28 | ""; 29 | String actual = "" + 30 | "{\n" + 31 | " \"b\": \"val2\",\n" + 32 | " \"d\": true,\n" + 33 | " \"e\": 1024,\n" + 34 | " \"f\": null,\n" + 35 | " \"c\": [\n" + 36 | " \"text\",\n" + 37 | " null,\n" + 38 | " true,\n" + 39 | " 1,\n" + 40 | " {\n" + 41 | " \"c2\": \"some other text\",\n" + 42 | " \"c1\": \"some text\"\n" + 43 | " }\n" + 44 | " ],\n" + 45 | " \"a\": \"val1\",\n" + 46 | " \"g\": \"\",\n" + 47 | " \"h\": \"text again\"\n" + 48 | "}" + 49 | ""; 50 | JSONCompare.assertMatches(expected, actual); 51 | } 52 | 53 | @Test 54 | public void compareSimple_negative() { 55 | String expected = "" + 56 | "{\n" + 57 | " \"a\": \"val1\",\n" + 58 | " \"b\": \"val2\",\n" + 59 | " \"c\": [\n" + 60 | " 1,\n" + 61 | " null,\n" + 62 | " true,\n" + 63 | " \"text\",\n" + 64 | " {\n" + 65 | " \"c1\": \"some text\"\n" + 66 | " }\n" + 67 | " ],\n" + 68 | " \"d\": true,\n" + 69 | " \"e\": 1024,\n" + 70 | " \"f\": null,\n" + 71 | " \"g\": \"\"\n" + 72 | "}" + 73 | ""; 74 | String actual = "" + 75 | "{\n" + 76 | " \"b\": \"val2\",\n" + 77 | " \"d\": true,\n" + 78 | " \"e\": 1024,\n" + 79 | " \"f\": null,\n" + 80 | " \"c\": [\n" + 81 | " \"text\",\n" + 82 | " null,\n" + 83 | " true,\n" + 84 | " 1,\n" + 85 | " {\n" + 86 | " \"c2\": \"some other text\",\n" + 87 | " \"c1\": \"DIFFERS HERE\"\n" + 88 | " }\n" + 89 | " ],\n" + 90 | " \"a\": \"val1\",\n" + 91 | " \"g\": \"\",\n" + 92 | " \"h\": \"text again\"\n" + 93 | "}" + 94 | ""; 95 | JSONCompare.assertNotMatches(expected, actual); 96 | } 97 | 98 | @Test 99 | public void compareSimpleViaDoNotFindUseCase() { 100 | String expected = "" + 101 | "{\n" + 102 | " \"a\": \"val1\",\n" + 103 | " \"b\": \"val2\",\n" + 104 | " \"c\": [\n" + 105 | " 1,\n" + 106 | " null,\n" + 107 | " true,\n" + 108 | " \"text\",\n" + 109 | " {\n" + 110 | " \"c1\": \"some text\"\n" + 111 | " }\n" + 112 | " ],\n" + 113 | " \"d\": true,\n" + 114 | " \"e\": 1024,\n" + 115 | " \"f\": null,\n" + 116 | " \"!field\": \"DON'T CARE ABOUT THE VALUE HERE\"\n" + 117 | "}" + 118 | ""; 119 | String actual = "" + 120 | "{\n" + 121 | " \"b\": \"val2\",\n" + 122 | " \"d\": true,\n" + 123 | " \"e\": 1024,\n" + 124 | " \"f\": null,\n" + 125 | " \"c\": [\n" + 126 | " \"text\",\n" + 127 | " null,\n" + 128 | " true,\n" + 129 | " 1,\n" + 130 | " {\n" + 131 | " \"c2\": \"some other text\",\n" + 132 | " \"c1\": \"some text\"\n" + 133 | " }\n" + 134 | " ],\n" + 135 | " \"a\": \"val1\",\n" + 136 | " \"g\": \"\",\n" + 137 | " \"h\": \"text again\"\n" + 138 | "}" + 139 | ""; 140 | JSONCompare.assertMatches(expected, actual); 141 | } 142 | 143 | @Test 144 | public void compareSimpleViaDoNotFindUseCase_negative() { 145 | String expected = "" + 146 | "{\n" + 147 | " \"a\": \"val1\",\n" + 148 | " \"b\": \"val2\",\n" + 149 | " \"c\": [\n" + 150 | " 1,\n" + 151 | " null,\n" + 152 | " true,\n" + 153 | " \"text\",\n" + 154 | " {\n" + 155 | " \"c1\": \"some text\"\n" + 156 | " }\n" + 157 | " ],\n" + 158 | " \"d\": true,\n" + 159 | " \"e\": 1024,\n" + 160 | " \"f\": null,\n" + 161 | " \"!h\": \"DON'T CARE ABOUT THE VALUE HERE\"\n" + 162 | "}" + 163 | ""; 164 | String actual = "" + 165 | "{\n" + 166 | " \"b\": \"val2\",\n" + 167 | " \"d\": true,\n" + 168 | " \"e\": 1024,\n" + 169 | " \"f\": null,\n" + 170 | " \"c\": [\n" + 171 | " \"text\",\n" + 172 | " null,\n" + 173 | " true,\n" + 174 | " 1,\n" + 175 | " {\n" + 176 | " \"c2\": \"some other text\",\n" + 177 | " \"c1\": \"some text\"\n" + 178 | " }\n" + 179 | " ],\n" + 180 | " \"a\": \"val1\",\n" + 181 | " \"g\": \"\",\n" + 182 | " \"h\": \"text again\"\n" + 183 | "}" + 184 | ""; 185 | JSONCompare.assertNotMatches(expected, actual); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONRegexCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.CompareMode; 4 | import io.json.compare.JSONCompare; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Collections; 8 | import java.util.HashSet; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertThrows; 11 | 12 | public class JSONRegexCompareTests { 13 | 14 | @Test 15 | public void compareWithInvalidFieldRegex() { 16 | String expected = "{\"[a\":\"\\\\d+\"}"; 17 | String actual = "{\"[a\":10}"; 18 | JSONCompare.assertMatches(expected, actual); 19 | } 20 | 21 | @Test 22 | public void compareWithInvalidRegexAndDifferentCases() { 23 | String expected = "{\"[a\":\"(test\"}"; 24 | String actual = "{\"[A\":\"(Test\"}"; 25 | JSONCompare.assertNotMatches(expected, actual); 26 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 27 | String actual1 = "{\"[a\":\"(test\"}"; 28 | JSONCompare.assertMatches(expected, actual1); 29 | } 30 | 31 | @Test 32 | public void compareWithInvalidFieldRegexWithCompareModeRegexDisabled() { 33 | String expected = "{\"[a\":\"10\"}"; 34 | String actual = "{\"[a\":10}"; 35 | JSONCompare.assertMatches(expected, actual, new HashSet<>(Collections.singletonList(CompareMode.REGEX_DISABLED))); 36 | 37 | String expected1 = "{\"[a\":\"\\\\d+\"}"; 38 | assertThrows(AssertionError.class, () -> 39 | JSONCompare.assertMatches(expected1, actual, new HashSet<>(Collections.singletonList(CompareMode.REGEX_DISABLED)))); 40 | JSONCompare.assertNotMatches(expected1, actual, new HashSet<>(Collections.singletonList(CompareMode.REGEX_DISABLED))); 41 | } 42 | 43 | @Test 44 | public void compareWithSimpleRegex() { 45 | String expected = "{\"a\":\".*me.*\"}"; 46 | String actual = "{\"a\":\"some text\"}"; 47 | JSONCompare.assertMatches(expected, actual); 48 | } 49 | 50 | @Test 51 | public void compareWithSimpleRegex_negative() { 52 | String expected = "{\"a\":\".*me.*\"}"; 53 | String actual = "{\"a\":\"som text\"}"; 54 | JSONCompare.assertNotMatches(expected, actual); 55 | } 56 | 57 | @Test 58 | public void compareWithNegativeLookAheadRegex_negative() { 59 | String expected = "{\"(?!a.*).*\":\".*\"}"; 60 | String actual = "{\"ab\":\"som text\"}"; 61 | JSONCompare.assertNotMatches(expected, actual); 62 | expected = "{\"(?!x.*).*\":\"blah\"}"; 63 | JSONCompare.assertNotMatches(expected, actual); 64 | expected = "{\"(?!x.*).*\":\".*\"}"; 65 | JSONCompare.assertMatches(expected, actual); 66 | } 67 | 68 | @Test 69 | public void compareWithEmptyValue() { 70 | String expected = "{\"a\":\".*\"}"; 71 | String actual = "{\"a\":\"\"}"; 72 | JSONCompare.assertMatches(expected, actual); 73 | } 74 | 75 | @Test 76 | public void compareWithEmptyValue_negative() { 77 | String expected = "{\"a\":\"!.*\"}"; 78 | String actual = "{\"a\":\"\"}"; 79 | JSONCompare.assertNotMatches(expected, actual); 80 | } 81 | 82 | @Test 83 | public void compareWithNumbers() { 84 | String expected = "{\"a\":\"\\\\d+\"}"; 85 | String actual = "{\"a\":10}"; 86 | JSONCompare.assertMatches(expected, actual); 87 | } 88 | 89 | @Test 90 | public void compareWithNumbers_negative() { 91 | String expected = "{\"a\":\"\\\\d+\"}"; 92 | String actual = "{\"a\":\"notanumber\"}"; 93 | JSONCompare.assertNotMatches(expected, actual); 94 | } 95 | 96 | @Test 97 | public void compareWithEscapedRegex() { 98 | String expected = "{\"a\":\"\\\\Q\\\\d+\\\\E\"}"; 99 | String actual = "{\"a\":\"\\\\d+\"}"; 100 | JSONCompare.assertMatches(expected, actual); 101 | } 102 | 103 | @Test 104 | public void compareWithEscapedRegex_negative() { 105 | String expected = "{\"a\":\"\\\\d+\"}"; 106 | String actual = "{\"a\":\"\\\\d+\"}"; 107 | JSONCompare.assertNotMatches(expected, actual); 108 | } 109 | 110 | @Test 111 | public void compareFields() { 112 | String expected = "{\".*oba.*\":\"some value\"}"; 113 | String actual = "{\"foobar\":\"some value\"}"; 114 | JSONCompare.assertMatches(expected, actual); 115 | } 116 | 117 | @Test 118 | public void compareFields_negative() { 119 | String expected = "{\".*oba.*\":\"some value\"}"; 120 | String actual = "{\"barfoo\":\"some value\",\"a\":\"some value\"}"; 121 | JSONCompare.assertNotMatches(expected, actual); 122 | } 123 | 124 | @Test 125 | public void compareDoNotFindFields() { 126 | String expected = "{\"!.*oba.*\":\"field should not be found\",\"a\":\"some value\"}"; 127 | String actual = "{\"barfoo\":\"field should not be found\",\"a\":\"some value\"}"; 128 | JSONCompare.assertMatches(expected, actual); 129 | } 130 | 131 | @Test 132 | public void compareDoNotFindFields_negative() { 133 | String expected = "{\"!.*oba.*\":\"field should not be found\"}"; 134 | String actual = "{\"a\":\"val1\", \"foobar\":\"field should not be found\"}"; 135 | JSONCompare.assertNotMatches(expected, actual); 136 | } 137 | 138 | @Test 139 | /** 140 | * Pattern.quote() method wraps the string in \Q...\E, which turns the text is into a regex 141 | * literal. 142 | */ 143 | public void compareWithRegexLiteral() { 144 | String expected = "{\"a\":\"\\\\Q1\\\\d+4\\\\E\"}"; 145 | String actual = "{\"a\":\"1\\\\d+4\"}"; 146 | JSONCompare.assertMatches(expected, actual); 147 | } 148 | 149 | @Test 150 | public void compareWithRegexLiteral_negative() { 151 | String expected = "{\"a\":\"1\\\\d+4\"}"; 152 | String actual = "{\"a\":\"1\\\\d+4\"}"; 153 | JSONCompare.assertNotMatches(expected, actual); 154 | } 155 | 156 | @Test 157 | public void compareArray() { 158 | String expected = "[1,true,\"\\\\d+\",\"text\"]"; 159 | String actual = "[\"text\",1,true,240]"; 160 | JSONCompare.assertMatches(expected, actual); 161 | } 162 | 163 | @Test 164 | public void compareArray_negative() { 165 | String expected = "[\"1\",true,\"!\\\\d+\",\"text\"]"; 166 | String actual = "[\"text\",1,true,240]"; 167 | JSONCompare.assertNotMatches(expected, actual); 168 | } 169 | 170 | @Test 171 | public void compareWithSpecialCharacters() { 172 | String expected = "{\"\\\\Q@!#$%^&*()\\\\E\":\"@!#$%^&*()[]{};'<./?.,\"}"; 173 | String actual = "{\"@!#$%^&*()\":\"@!#$%^&*()[]{};'<./?.,\"}"; 174 | JSONCompare.assertMatches(expected, actual); 175 | } 176 | 177 | @Test 178 | public void compareWithSpecialCharacters_negative() { 179 | String expected = "{\"\\\\Q@!#$%^&*()\\\\E\":\"!@#$%^&*()[]{};'<./?.,\"}"; 180 | String actual = "{\"@!#$%^&*()a\":\"!@#$%^&*()[]{};'<./?.,\"}"; 181 | JSONCompare.assertNotMatches(expected, actual); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/diffs/JsonDiffsAsListTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.diffs; 2 | 3 | import io.json.compare.CompareMode; 4 | import io.json.compare.JSONCompare; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | class JsonDiffsAsListTests { 15 | 16 | @Test 17 | void compareJsonArraysAndCheckForFor1ElementNotFoundDifference() { 18 | String expected = "[\"a\",\"c\",1,2,true,false,12.091,null]"; 19 | String actual = "[\"a\",\"b\",1,2,true,false,12.091,null]"; 20 | List diffs = JSONCompare.diffs(expected, actual); 21 | assertEquals(1, diffs.size()); 22 | assertTrue(diffs.get(0).matches("(?s).*\\Q$[1]\\E was not found.*\"c\".*")); 23 | } 24 | 25 | @Test 26 | void compareJsonArraysAndCheckForMultipleElementNotFoundDifferences() { 27 | String expected = "[\"a\",\"c\",1,200,true,false,12.092,null,{\"lorem\":\"ipsum\"}]"; 28 | String actual = "[12.091,10,\"b\",1,\"a\",2,true,{\"lorem\":\"ipsum-updated\"},\"some text\",false]"; 29 | List diffs = JSONCompare.diffs(expected, actual); 30 | assertEquals(5, diffs.size()); 31 | assertTrue(diffs.get(0).matches("(?s).*\\Q$[1]\\E was not found.*\"c\".*")); 32 | assertTrue(diffs.get(1).matches("(?s).*\\Q$[3]\\E was not found.*200.*")); 33 | assertTrue(diffs.get(2).matches("(?s).*\\Q$[6]\\E was not found.*12.092.*")); 34 | assertTrue(diffs.get(3).matches("(?s).*\\Q$[7]\\E was not found.*null.*")); 35 | assertTrue(diffs.get(4).matches("(?s).*\\Q$[8]\\E was not found.*\\{.*\"lorem\".*\"ipsum\".*}.*")); 36 | } 37 | 38 | @Test 39 | void compareJsonArraysAndCheckForOneMatchAnyDifferences() { 40 | String expected = "[\"a\",\".*\",1,\".*\",\".*\"]"; 41 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 42 | List diffs = JSONCompare.diffs(expected, actual); 43 | assertEquals(1, diffs.size()); 44 | assertTrue(diffs.get(0).matches("(?s).*\\Q$[4]\\E -> Expected condition \"\\Q.*\\E\" was not met. Actual JSON ARRAY has no extra elements.*")); 45 | } 46 | 47 | @Test 48 | void compareJsonArraysAndCheckForOneDoNotMatchAnyDifferences() { 49 | String expected = "[{\"lorem\":\"ipsum\"},\"1\",\"!.*\"]"; 50 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 51 | List diffs = JSONCompare.diffs(expected, actual); 52 | assertEquals(1, diffs.size()); 53 | assertTrue(diffs.get(0).matches("(?s).*\\Q$[2]\\E -> Expected condition \"\\Q!.*\\E\" was not met. Actual JSON ARRAY has extra elements.*")); 54 | } 55 | 56 | @Test 57 | void compareJsonArraysAndCheckForJsonNonExtensibleAndAndJsonStrictOrderDifferences() { 58 | String expected = "[{\"lorem\":\"ipsum\"},\"c\",\"!1\",true]"; 59 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"},-10.02]"; 60 | List diffs = JSONCompare.diffs(expected, actual, 61 | new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE, CompareMode.JSON_ARRAY_STRICT_ORDER))); 62 | assertEquals(5, diffs.size()); 63 | assertTrue(diffs.get(0).matches("(?s).*\\Q$[0]\\E -> Different JSON types: expected ObjectNode but got TextNode.*")); 64 | assertTrue(diffs.get(1).matches("(?s).*\\Q$[1]\\E.*Expected value: \"c\" But got: true.*")); 65 | assertTrue(diffs.get(2).matches("(?s).*\\Q$[2]\\E was found.*\"!1\".*")); 66 | assertTrue(diffs.get(3).matches("(?s).*\\Q$[3]\\E -> Different JSON types: expected BooleanNode but got ObjectNode.*")); 67 | assertTrue(diffs.get(4).matches("(?s).*\\Q$\\E -> Actual JSON ARRAY has extra elements.*")); 68 | } 69 | 70 | @Test 71 | void compareJsonsWithCUstomComparator() { 72 | String expected = "{\"name\":\"test1\",\"records\":[3,4]}"; 73 | String actual = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 74 | List diffs = JSONCompare.diffs(expected, actual, new JsonCustomComparatorDiffTests.CustomComparator()); 75 | assertEquals(2, diffs.size()); 76 | assertTrue(diffs.get(0).matches("(?s).*\\Q$.name\\E.*Expected value: \"test1\" But got: \"test\".*")); 77 | assertTrue(diffs.get(1).matches("(?s).*\\Q$.records[1]\\E.* was not found.*4.*")); 78 | } 79 | 80 | @Test 81 | void compareJsonObjectsAndCheckForMultipleInDepthFieldNotFoundDifferences() { 82 | String expected = "{\"a\":100,\"x\":51,\"b\":{\"b1\":\"val1\",\"b2\":{\"b21\":\"test\"}}}"; 83 | String actual = "{\"b\":{\"b3\":\"val1\",\"b2\":{\"b22\":10.432}},\"a\":100,\"c\":true}"; 84 | List diffs = JSONCompare.diffs(expected, actual); 85 | assertEquals(3, diffs.size()); 86 | assertTrue(diffs.get(0).matches("(?s).*\\Q$.x\\E was not found.*")); 87 | assertTrue(diffs.get(1).matches("(?s).*\\Q$.b.b1\\E was not found.*")); 88 | assertTrue(diffs.get(2).matches("(?s).*\\Q$.b.b2.b21\\E was not found.*")); 89 | } 90 | 91 | @Test 92 | void checkMultipleJsonPathDifferences() { 93 | String expected = "{\"#($.a.length())\":3,\"b\":\"val1\",\"x\":{\"x1\":{\"y11\":{\"#($.www)\":\"lorem1\"}}},\"z\":[{\"#($.length())\":1}]," + 94 | "\"u\":{\"#($.u1)\":{\"u11\":20209}}}"; 95 | String actual = "{\"z\":[2,3,4],\"x\":{\"x2\":290.11,\"x1\":{\"x11\":null,\"y11\":{\"a\":\"lorem2\"}}},\"b\":\"val2\",\"a\":[4,5]," + 96 | "\"u\":{\"u1\":{\"u11\":20000}}}"; 97 | List diffs = JSONCompare.diffs(expected, actual); 98 | assertEquals(5, diffs.size()); 99 | assertTrue(diffs.get(0).matches("(?s).*\\Q$.#($.a.length())\\E.*Expected json path result.*" + 100 | "3.*But got.*2.*")); 101 | assertTrue(diffs.get(1).matches("(?s).*\\Q$.b\\E.*Expected value: \"val1\" But got: \"val2\".*")); 102 | assertTrue(diffs.get(2).matches("(?s).*\\Q$.x.x1.y11.#($.www)\\E -> Json path -> No results for path.*")); 103 | assertTrue(diffs.get(3).matches("(?s).*\\Q$.z.#($.length())\\E.*Expected json path result.*1.*But got.*3.*")); 104 | assertTrue(diffs.get(4).matches("(?s).*\\Q$.u.#($.u1).u11\\E.*Expected value: 20209 But got: 20000" + 105 | ".*Expected json path result.*u11.*20209.*20000.*")); 106 | } 107 | 108 | 109 | @Test 110 | void compareJsonsWithCustomComparatorAndCompareModes() { 111 | String expected = "{\"name\":\".*test\",\"records\":[3,1]}"; 112 | String actual = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 113 | List diffs = JSONCompare.diffs(expected, actual, new JsonCustomComparatorDiffTests.CustomComparator(), 114 | new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE, CompareMode.JSON_ARRAY_NON_EXTENSIBLE))); 115 | assertEquals(3, diffs.size()); 116 | assertTrue(diffs.get(0).matches("(?s).*\\Q$.name\\E.*Expected value: \".*test\" But got: \"test\".*")); 117 | assertTrue(diffs.get(1).matches("(?s).*\\Q$.records\\E ->.*Actual JSON ARRAY has extra elements.*")); 118 | assertTrue(diffs.get(2).matches("(?s).*\\Q$\\E -> Actual JSON OBJECT has extra fields.*")); 119 | } 120 | 121 | @Test 122 | void compareJsonsThatMatch() { 123 | String expected = "{\"name\":\"test\",\"records\":[3,1]}"; 124 | String actual = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 125 | List diffs = JSONCompare.diffs(expected, actual); 126 | assertEquals(0, diffs.size()); 127 | } 128 | } -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/JSONUseCaseCompareTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class JSONUseCaseCompareTests { 7 | 8 | @Test 9 | public void compareViaDoNotFindUseCaseOnValue() { 10 | String expected = "{\"a\":\"!test\"}"; 11 | String actual = "{\"a\":\"testing\"}"; 12 | JSONCompare.assertMatches(expected, actual); 13 | } 14 | 15 | @Test 16 | public void compareViaDoNotFindUseCaseOn_negative() { 17 | String expected = "{\"a\":\"!test\"}"; 18 | String actual = "{\"a\":\"test\"}"; 19 | JSONCompare.assertNotMatches(expected, actual); 20 | } 21 | 22 | @Test 23 | public void compareViaDoNotFindUseCaseOnField() { 24 | String expected = "{\"!a\":\"value does not matter\"}"; 25 | String actual = "{\"ab\":\"value does not matter\"}"; 26 | JSONCompare.assertMatches(expected, actual); 27 | } 28 | 29 | @Test 30 | public void compareViaDoNotFindUseCaseOnField_negative() { 31 | String expected = "{\"!a\":\"value does not matter\"}"; 32 | String actual = "{\"a\":\"value does not matter\"}"; 33 | JSONCompare.assertNotMatches(expected, actual); 34 | } 35 | 36 | @Test 37 | public void compareLiteral() { 38 | String expected = "{\"a\":\"\\\\!some \\\\\\\\text\"}"; 39 | String actual = "{\"a\":\"!some \\\\text\"}"; 40 | JSONCompare.assertMatches(expected, actual); 41 | } 42 | 43 | @Test 44 | public void compareLiteral_negative() { 45 | String expected = "{\"a\":\"\\\\\\\\!some text\"}"; 46 | String actual = "{\"a\":\"!some text\"}"; 47 | JSONCompare.assertNotMatches(expected, actual); 48 | } 49 | 50 | @Test 51 | public void compareMultipleEscapedLiteral() { 52 | String expected = "{\"a\":\"\\\\\\\\!some text\"}"; 53 | String actual = "{\"a\":\"\\\\!some text\"}"; 54 | JSONCompare.assertMatches(expected, actual); 55 | } 56 | 57 | @Test 58 | public void compareMultipleEscapedLiteral_negative() { 59 | String expected = "{\"a\":\"\\\\!some text\"}"; 60 | String actual = "{\"a\":\"\\\\!some text\"}"; 61 | JSONCompare.assertNotMatches(expected, actual); 62 | } 63 | 64 | @Test 65 | public void compareJsonObjectsViaFindAnyUseCase() { 66 | String expected = "{\"a\":\".*\"}"; 67 | String actual = "{\"a\": 1}"; 68 | JSONCompare.assertMatches(expected, actual); 69 | } 70 | 71 | @Test 72 | public void compareJsonObjectsViaFindAnyUseCase_a() { 73 | String expected = "{\"a\":\"\\\\.*\"}"; 74 | String actual = "{\"a\": 1}"; 75 | JSONCompare.assertMatches(expected, actual); 76 | } 77 | 78 | @Test 79 | public void compareJsonObjectsViaFindAnyUseCase_negative() { 80 | String expected = "{\"a\":\"\\\\.*\"}"; 81 | String actual = "{\"a\": [1]}"; 82 | JSONCompare.assertNotMatches(expected, actual); 83 | } 84 | 85 | @Test 86 | public void compareJsonObjectsViaFindAnyUseCase_negative1() { 87 | String expected = "{\"a\":\".*\"}"; 88 | String actual = "{\"c\": \"0\"}"; 89 | JSONCompare.assertNotMatches(expected, actual); 90 | } 91 | 92 | @Test 93 | public void compareJsonObjectsViaFindAnyUseCase1() { 94 | String expected = "{\".*\":\".*\"}"; 95 | String actual = "{\"c\": [1]}"; 96 | JSONCompare.assertMatches(expected, actual); 97 | } 98 | 99 | @Test 100 | public void compareJsonObjectsViaFindAnyUseCase1_negative() { 101 | String expected = "{\".*\":\".*\"}"; 102 | String actual = "[\"c\"]"; 103 | JSONCompare.assertNotMatches(expected, actual); 104 | } 105 | 106 | @Test 107 | public void compareJsonObjectsViaFindAnyUseCase2() { 108 | String expected = "{\".*\":\".*\"}"; 109 | String actual = "{\"c\": {\"a\": true}}"; 110 | JSONCompare.assertMatches(expected, actual); 111 | } 112 | 113 | @Test 114 | public void compareJsonObjectsViaFindAnyUseCase3() { 115 | String expected = "{\"a\":[\".*\"]}"; 116 | String actual = "{\"a\": [1]}"; 117 | JSONCompare.assertMatches(expected, actual); 118 | } 119 | 120 | @Test 121 | public void compareJsonObjectsViaFindAnyUseCase3_negative() { 122 | String expected = "{\"a\":[\".*\"]}"; 123 | String actual = "{\"a\": {\"b\":0}}"; 124 | JSONCompare.assertNotMatches(expected, actual); 125 | } 126 | 127 | @Test 128 | public void compareJsonArraysViaFindAnyUseCase() { 129 | String expected = "[\".*\"]"; 130 | String actual = "[1]"; 131 | JSONCompare.assertMatches(expected, actual); 132 | } 133 | 134 | @Test 135 | public void compareJsonArraysViaFindAnyUseCase_negative() { 136 | String expected = "[\".*\"]"; 137 | String actual = "[]"; 138 | JSONCompare.assertNotMatches(expected, actual); 139 | } 140 | 141 | @Test 142 | public void compareJsonArraysViaFindAnyUseCase_negative1() { 143 | String expected = "[\".*\"]"; 144 | String actual = "{\"a\":true}"; 145 | JSONCompare.assertNotMatches(expected, actual); 146 | } 147 | 148 | @Test 149 | public void compareJsonArraysViaFindAnyUseCase1() { 150 | String expected = "[\".*\"]"; 151 | String actual = "[{\"a\":0}]"; 152 | JSONCompare.assertMatches(expected, actual); 153 | } 154 | 155 | @Test 156 | public void compareJsonObjectsViaDoNotFindAnyUseCase_negative() { 157 | String expected = "{\"!.*\":\".*\"}"; 158 | String actual = "{\"c\": {\"a\": true}}"; 159 | JSONCompare.assertNotMatches(expected, actual); 160 | } 161 | 162 | @Test 163 | public void compareJsonArraysViaFindAnyUseCase2() { 164 | String expected = "[1 , \".*\"]"; 165 | String actual = "[{\"a\":0}, 1]"; 166 | JSONCompare.assertMatches(expected, actual); 167 | } 168 | 169 | @Test 170 | public void compareJsonArraysViaFindAnyUseCase2_negative() { 171 | String expected = "[1 , \".*\"]"; 172 | String actual = "[1]"; 173 | JSONCompare.assertNotMatches(expected, actual); 174 | } 175 | 176 | @Test 177 | public void compareJsonArraysViaFindAnyUseCase2a() { 178 | String expected = "[1 , \"\\\\.*\"]"; 179 | String actual = "[\"c\", 1]"; 180 | JSONCompare.assertMatches(expected, actual); 181 | } 182 | 183 | @Test 184 | public void compareJsonArraysViaFindAnyUseCase2a_negative() { 185 | String expected = "[1 , \"\\\\.*\"]"; 186 | String actual = "[{\"a\":0}, 1]"; 187 | JSONCompare.assertNotMatches(expected, actual); 188 | } 189 | 190 | @Test 191 | public void compareJsonArraysViaDoNotFindAnyUseCase() { 192 | String expected = "[\"!.*\"]"; 193 | String actual = "[]"; 194 | JSONCompare.assertMatches(expected, actual); 195 | } 196 | 197 | @Test 198 | public void compareJsonArraysViaDoNotFindAnyUseCase_negative() { 199 | String expected = "[\"!.*\"]"; 200 | String actual = "[{\"a\":0}]"; 201 | JSONCompare.assertNotMatches(expected, actual); 202 | } 203 | 204 | @Test 205 | public void compareJsonArraysViaDoNotFindAnyUseCase1() { 206 | String expected = "[1,{\"a\":\".*\"},\"!.*\"]"; 207 | String actual = "[{\"a\":0},1]"; 208 | JSONCompare.assertMatches(expected, actual); 209 | } 210 | 211 | @Test 212 | public void compareJsonArraysViaDoNotFindAnyUseCase1_negative() { 213 | String expected = "[1,{\"a\":\".*\"},\"!.*\"]"; 214 | String actual = "[{\"a\":0},1,true]"; 215 | JSONCompare.assertNotMatches(expected, actual); 216 | } 217 | 218 | @Test 219 | public void compareJsonArraysViaDoNotFindAnyUseCaseEscaped() { 220 | String expected = "[1,2,\"\\\\Q!.*\\\\E\"]"; 221 | String actual = "[2,\"!.*\",1]"; 222 | JSONCompare.assertMatches(expected, actual); 223 | } 224 | 225 | @Test 226 | public void compareJsonArraysViaDoNotFindAnyUseCaseEscaped_negative() { 227 | String expected = "[1,2,\"\\\\Q!.*\\\\E\"]"; 228 | String actual = "[2,\"!.*\",1]"; 229 | JSONCompare.assertMatches(expected, actual); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/diffs/JsonCustomComparatorDiffTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.diffs; 2 | 3 | import io.json.compare.CompareMode; 4 | import io.json.compare.JSONCompare; 5 | import io.json.compare.JsonComparator; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.Arrays; 9 | import java.util.HashSet; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertThrows; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | class JsonCustomComparatorDiffTests { 15 | 16 | @Test 17 | void compareJsons() { 18 | String expected = "{\"name\":\"test\",\"records\":[3]}"; 19 | String actual = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 20 | JSONCompare.assertMatches(expected, actual, new CustomComparator()); 21 | 22 | String expected1 = "{\"name\":\"test1\",\"records\":[3,4]}"; 23 | String actual1 = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 24 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected1, actual1, new CustomComparator())); 25 | assertTrue(error.getMessage().matches("(?s).*FOUND 2 DIFFERENCE.*" + 26 | "\\Q$.name\\E.*Expected value: \"test1\" But got: \"test\".*" + 27 | "\\Q$.records[1]\\E was not found.*4.*")); 28 | } 29 | 30 | @Test 31 | void compareJsonsWithCompareModes() { 32 | String expected = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 33 | String actual = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 34 | JSONCompare.assertMatches(expected, actual, new CustomComparator(), 35 | new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE, CompareMode.JSON_ARRAY_NON_EXTENSIBLE, 36 | CompareMode.JSON_ARRAY_STRICT_ORDER))); 37 | 38 | String expected1 = "{\"name\":\"test\",\"records\":[2,1]}"; 39 | String actual1 = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 40 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected1, actual1, new CustomComparator(), 41 | new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE, CompareMode.JSON_ARRAY_NON_EXTENSIBLE, 42 | CompareMode.JSON_ARRAY_STRICT_ORDER)))); 43 | assertTrue(error.getMessage().matches("(?s).*FOUND 4 DIFFERENCE.*" + 44 | "\\Q$.records[0]\\E.*Expected value: 2 But got: 1.*" + 45 | "\\Qrecords[1]\\E.*Expected value: 1 But got: 2.*" + 46 | "\\Qrecords\\E -> Actual JSON ARRAY has extra elements.*" + 47 | "\\Q$\\E -> Actual JSON OBJECT has extra fields.*")); 48 | 49 | String expected2 = "{\"name\":\"test\",\"records\":[1,2]}"; 50 | String actual2 = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 51 | error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected2, actual2, new CustomComparator(), 52 | new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE, CompareMode.JSON_ARRAY_NON_EXTENSIBLE, 53 | CompareMode.JSON_ARRAY_STRICT_ORDER)))); 54 | assertTrue(error.getMessage().matches("(?s).*FOUND 2 DIFFERENCE.*" + 55 | "\\Q$.records\\E -> Actual JSON ARRAY has extra elements.*" + 56 | "\\Q$\\E -> Actual JSON OBJECT has extra fields.*")); 57 | 58 | String expected3 = "{\"name\":\"test\",\"records\":[1,2,3]}"; 59 | String actual3 = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 60 | error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected3, actual3, new CustomComparator(), 61 | new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE, CompareMode.JSON_ARRAY_NON_EXTENSIBLE, 62 | CompareMode.JSON_ARRAY_STRICT_ORDER)))); 63 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 64 | "\\Q$\\E -> Actual JSON OBJECT has extra fields.*")); 65 | } 66 | 67 | @Test 68 | void compareJsonsWithUseCases() { 69 | String expected = "{\"!name\":\"test\",\"records\":[1,2,3, \"!.*\"], \"otherRecords\":[4, \"!.*\"]}"; 70 | String actual = "{\"names\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 71 | JSONCompare.assertMatches(expected, actual, new CustomComparator()); 72 | 73 | expected = "{\"!name\":\"test\", \"records\":[1,2,3, \"!.*\"], \"otherRecords\":[4, \"!.*\"], \"!.*\":\".*\"}"; 74 | actual = "{\"records\":[1,2,3], \"otherRecords\":[4]}"; 75 | JSONCompare.assertMatches(expected, actual, new CustomComparator()); 76 | 77 | expected = "{\".*\":\"test\",\"records\":[1, \".*\", 3, \"!.*\"], \"otherRecords\":[4, \"!.*\"], \"!.*\":\".*\"}"; 78 | actual = "{\"names\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 79 | JSONCompare.assertMatches(expected, actual, new CustomComparator()); 80 | 81 | String expected1 = "{\".*\":\"test\", \"records\":[1, \".*\", 3, \"!.*\"], \"otherRecords\":[4, \"!.*\"], \"!.*\":\".*\"}"; 82 | String actual1 = "{\"names\":\"test1\", \"records\":[2,1,4,3], \"otherRecords\":[1,2], \"another\":\"record\"}"; 83 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected1, actual1, new CustomComparator())); 84 | assertTrue(error.getMessage().matches("(?s).*FOUND 8 DIFFERENCE.*" + 85 | "\\Q$..*\\E.*Expected value: \"test\" But got: \"test1\".*" + 86 | "\\Q$..*\\E.*Different JSON types: expected TextNode but got ArrayNode.*" + 87 | "\\Q$..*\\E.*Different JSON types: expected TextNode but got ArrayNode.*" + 88 | "\\Q$..*\\E.*Expected value: \"test\" But got: \"record\".*" + 89 | "\\Q$.records[3]\\E ->.*Expected condition \"\\Q!.*\\E\" was not met. Actual JSON ARRAY has extra elements.*" + 90 | "\\Q$.otherRecords[0]\\E was not found.*4.*" + 91 | "\\Q$.otherRecords[1]\\E ->.*Expected condition \"\\Q!.*\\E\" was not met. Actual JSON ARRAY has extra elements.*" + 92 | "\\Q$.\"!.*\"\\E condition was not met. Actual JSON OBJECT has extra fields.*")); 93 | 94 | String expected2 = "{\"name\":\"test\", \"records\":[1, \".*\", 3, 4, \".*\"], \"otherRecords\":[4, \"!.*\"], \".*\":\".*\"}"; 95 | String actual2 = "{\"names\":\"test1\", \"records\":[2,1,4,3], \"otherRecords\":[1,2, 4], \"another\":\"record\"}"; 96 | error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected2, actual2, new CustomComparator())); 97 | assertTrue(error.getMessage().matches("(?s).*FOUND 3 DIFFERENCE.*" + 98 | "\\Q$.name\\E was not found.*" + 99 | "\\Q$.records[4]\\E ->.*Expected condition \"\\Q.*\\E\" was not met. Actual JSON ARRAY has no extra elements.*" + 100 | "\\Q$.otherRecords[1]\\E ->.*Expected condition \"\\Q!.*\\E\" was not met. Actual JSON ARRAY has extra elements.*")); 101 | 102 | String expected3 = "{\"name\":\"test\", \"records\":[1, \".*\", 3, 4, \".*\"], \"otherRecords\":[4, 2, \"!.*\"], \".*\":\".*\"}"; 103 | String actual3 = "{\"name\":\"test\", \"records\":[2,1,5,4,3], \"otherRecords\":[2, 4]}"; 104 | error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected3, actual3, new CustomComparator())); 105 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 106 | "\\Q$..*\\E was not found.*")); 107 | } 108 | 109 | 110 | @Test 111 | void compareJsonsWithRegexes() { 112 | String expected = "{\"na.*\":\"test\",\"records\":[1,\"\\\\d+\",3], \"otherRecords\":[4]}"; 113 | String actual = "{\"na.*\":\"test\",\"records\":[\"\\\\d+\",3,1], \"otherRecords\":[4]}"; 114 | JSONCompare.assertMatches(expected, actual, new CustomComparator()); 115 | 116 | String expected1 = "{\"na.*\":\"test\",\"records\":[2,\"\\\\d+\"]}"; 117 | String actual1 = "{\"name\":\"test\",\"records\":[1,2,3], \"otherRecords\":[4]}"; 118 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected1, actual1, new CustomComparator())); 119 | assertTrue(error.getMessage().matches("(?s).*FOUND 2 DIFFERENCE.*" + 120 | "\\Q$.na.*\\E was not found.*" + 121 | "\\Q$.records[1]\\E was not found.*\"\\Q\\\\d+\\E\".*")); 122 | } 123 | 124 | public static class CustomComparator implements JsonComparator { 125 | @Override 126 | public boolean compareValues(Object expected, Object actual) { 127 | return expected.equals(actual); 128 | } 129 | 130 | @Override 131 | public boolean compareFields(String expected, String actual) { 132 | return expected.equals(actual); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/readme/ReadmeTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.readme; 2 | 3 | import io.json.compare.CompareMode; 4 | import io.json.compare.JSONCompare; 5 | import io.json.compare.JsonComparator; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.Arrays; 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertThrows; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | class ReadmeTests { 17 | 18 | @Test 19 | void matchJsonConvertibleJavaObjects() { 20 | String expectedString = """ 21 | { 22 | "string": "I'm on a seafood diet. I see food and I eat it!", 23 | "number": "\\\\d+.\\\\d+", 24 | "object": { 25 | "pun": "\\\\QWhy don't skeletons fight each other? They don't have the guts!\\\\E" 26 | }, 27 | "array": [".*", "\\\\d+", true, null], 28 | "boolean": "true|false" 29 | } 30 | """; 31 | String actualString = """ 32 | { 33 | "string": "I'm on a seafood diet. I see food and I eat it!", 34 | "number": 0.99, 35 | "object": { 36 | "pun": "Why don't skeletons fight each other? They don't have the guts!" 37 | }, 38 | "array": ["pancake", 18, true, null], 39 | "boolean": true 40 | } 41 | """; 42 | JSONCompare.assertMatches(expectedString, actualString); // assertion passes 43 | 44 | String anotherExpectedString = "{\"a\":1, \"b\": [4, \"ipsum\", \"\\\\d+\"]}"; 45 | 46 | // actual represented as Map 47 | Map actualMap = new HashMap<>(); 48 | actualMap.put("a", 1); 49 | actualMap.put("b", Arrays.asList("ipsum", 4, 5)); 50 | actualMap.put("c", true); 51 | JSONCompare.assertMatches(anotherExpectedString, actualMap); // assertion passes 52 | 53 | // Failed assertion 54 | String anotherActualString = "{\"a\":2, \"b\":[4, \"lorem\", 5], \"c\":true}"; 55 | JSONCompare.assertNotMatches(anotherExpectedString, anotherActualString); // assertion passes 56 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(anotherExpectedString, anotherActualString)); 57 | assertTrue(error.getMessage().matches("(?s).*FOUND 2 DIFFERENCE.*" + 58 | "\\Q$.a\\E.*Expected value: 1 But got: 2.*" + 59 | "\\Q$.b[1]\\E was not found.*")); 60 | } 61 | 62 | @Test 63 | void checkJsonInclusion() { 64 | // Check JSON object inclusion 65 | String expected = "{\"b\":\"val1\"}"; 66 | String actual = "{\"a\":\"val2\",\"b\":\"val1\"}"; 67 | JSONCompare.assertMatches(expected, actual); // assertion passes 68 | 69 | // JSON objects MUST have same sizes 70 | String expected1 = "{\"b\":\"val1\"}"; 71 | String actual1 = "{\"a\":\"val2\",\"b\":\"val1\"}"; 72 | JSONCompare.assertNotMatches(expected1, actual1, new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE))); // assertion passes 73 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected1, actual1, 74 | new HashSet<>(Arrays.asList(CompareMode.JSON_OBJECT_NON_EXTENSIBLE)))); 75 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 76 | "Actual JSON OBJECT has extra fields.*")); 77 | } 78 | 79 | @Test 80 | void checkJsonElementsOrder() { 81 | // JSON Array strict order is by default ignored 82 | String expected = "[\"lorem\", 2, false]"; 83 | String actual = "[false, 2, \"lorem\", 5, 4]"; 84 | JSONCompare.assertMatches(expected, actual); // assertion passes 85 | 86 | // Check JSON Array strict order 87 | String expected1 = "[\"lorem\", 2, false]"; 88 | String actual1 = "[false, 2, \"lorem\", 5, 4]"; 89 | JSONCompare.assertNotMatches(expected1, actual1, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_STRICT_ORDER))); // assertion passes 90 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected1, actual1, 91 | new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_STRICT_ORDER)))); 92 | assertTrue(error.getMessage().matches("(?s).*FOUND 2 DIFFERENCE.*" + 93 | "\\Q$[0]\\E.*" + 94 | "\\Q$[2]\\E.*")); 95 | } 96 | 97 | @Test 98 | void matchJsonRegexValues() { 99 | String expected = "{\"a\": \".*me.*\"}"; 100 | String actual = "{\"a\": \"some text\"}"; 101 | JSONCompare.assertMatches(expected, actual); // assertion passes 102 | } 103 | 104 | @Test 105 | void matchJsonRegexFields() { 106 | String expected = "{\".*oba.*\": \"some value\"}"; 107 | String actual = "{\"foobar\": \"some value\"}"; 108 | JSONCompare.assertMatches(expected, actual); // assertion passes 109 | } 110 | 111 | @Test 112 | void matchJsonRegexQuote() { 113 | String expected = "{\"a\":\"\\\\Qd+\\\\E\"}"; 114 | String actual = "{\"a\":\"d+\"}"; 115 | JSONCompare.assertMatches(expected, actual); // assertion passes 116 | } 117 | 118 | @Test 119 | void matchJsonRegexCustomComparator() { 120 | String expected = "{\"a\": \"\\\\d+\"}"; 121 | String actual = "{\"a\": \"\\\\d+\"}"; 122 | JSONCompare.assertMatches(expected, actual, new JsonComparator() { 123 | public boolean compareValues(Object expected, Object actual) { 124 | return expected.equals(actual); 125 | } 126 | 127 | public boolean compareFields(String expected, String actual) { 128 | return expected.equals(actual); 129 | } 130 | }); // assertion passes 131 | } 132 | 133 | @Test 134 | void matchJsonTweaksDoNotMatchValues() { 135 | String expected = "{\"a\": \"!test\"}"; 136 | String actual = "{\"a\": \"testing\"}"; 137 | JSONCompare.assertMatches(expected, actual); // assertion passes 138 | } 139 | 140 | @Test 141 | void matchJsonTweaksDoNotMatchFields() { 142 | String expected = "{\"!a\": \"value does not matter\"}"; 143 | String actual = "{\"b\": \"of course value does not matter\"}"; 144 | JSONCompare.assertMatches(expected, actual); // assertion passes 145 | } 146 | 147 | @Test 148 | void matchJsonTweaksNegativeLookaround() { 149 | String expected = "{\"(?!lorem.*).*\": \"valorem\"}"; 150 | String actual = "{\"ipsum\": \"valorem\"}"; 151 | JSONCompare.assertMatches(expected, actual); // assertion passes 152 | } 153 | 154 | @Test 155 | void matchJsonTweaksDoNotMatchAnyObjectFields() { 156 | String expected = "{\"b\": \"val1\", \"!.*\": \".*\"}"; 157 | String actual = "{\"a\": \"val2\", \"b\": \"val1\"}"; 158 | JSONCompare.assertNotMatches(expected, actual); 159 | } 160 | 161 | @Test 162 | void matchJsonTweaksDoNotMatchAnyArray() { 163 | String expected = "[false, \"test\", 4, \"!.*\"]"; 164 | String actual = "[4, false, \"test\", 1]"; 165 | JSONCompare.assertNotMatches(expected, actual); 166 | } 167 | 168 | @Test 169 | void matchJsonTweaksMatchAnyObjectFields() { 170 | String expected = "{\"b\": \"val1\", \".*\": \".*\"}"; 171 | String actual = "{\"b\": \"val1\"}"; 172 | JSONCompare.assertNotMatches(expected, actual); 173 | } 174 | 175 | @Test 176 | void matchJsonTweaksMatchAnyArray() { 177 | String expected = "[false, \"test\", 4, \".*\"]"; 178 | String actual = "[4, false, \"test\"]"; 179 | JSONCompare.assertNotMatches(expected, actual); 180 | } 181 | 182 | @Test 183 | void matchJsonTweaksJsonPath() { 184 | String expected = "{\"#($.store..isbn)\":[\"0-395-19395-8\",\"0-553-21311-3\",\"!.*\"]}"; 185 | String actual = "{\n" + 186 | " \"store\": {\n" + 187 | " \"book\": [\n" + 188 | " {\n" + 189 | " \"category\": \"reference\",\n" + 190 | " \"author\": \"Nigel Rees\",\n" + 191 | " \"title\": \"Sayings of the Century\",\n" + 192 | " \"price\": 8.95\n" + 193 | " },\n" + 194 | " {\n" + 195 | " \"category\": \"fiction\",\n" + 196 | " \"author\": \"Herman Melville\",\n" + 197 | " \"title\": \"Moby Dick\",\n" + 198 | " \"isbn\": \"0-553-21311-3\",\n" + 199 | " \"price\": 8.99\n" + 200 | " },\n" + 201 | " {\n" + 202 | " \"category\": \"fiction\",\n" + 203 | " \"author\": \"J. R. R. Tolkien\",\n" + 204 | " \"title\": \"The Lord of the Rings\",\n" + 205 | " \"isbn\": \"0-395-19395-8\",\n" + 206 | " \"price\": 22.99\n" + 207 | " }\n" + 208 | " ]\n" + 209 | " }\n" + 210 | "}"; 211 | JSONCompare.assertMatches(expected, actual); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue5Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | 8 | public class Issue5Test { 9 | 10 | @Test 11 | public void testIssue1() { 12 | String expected = 13 | "{\n" + 14 | " \"records\": [\n" + 15 | " {\n" + 16 | " \"name\": \"autodiscover.test.fr\",\n" + 17 | " \"type\": \"CNAME\",\n" + 18 | " \"content\": \".*\",\n" + 19 | " \"ttl\": \".*\",\n" + 20 | " \"!.*\": \".*\"\n" + 21 | " },\n" + 22 | " {\n" + 23 | " \"!name\": \".*\",\n" + 24 | " \"!type\": \".*\",\n" + 25 | " \"!content\": \".*\",\n" + 26 | " \"ttl\": \".*\"" + 27 | " }," + 28 | " {\n" + 29 | " \"name\": \"test.fr\",\n" + 30 | " \"type\": \"MX\",\n" + 31 | " \"content\": \".*\",\n" + 32 | " \"ttl\": \".*\",\n" + 33 | " \"prio\": \".*\",\n" + 34 | " \"!.*\": \".*\"\n" + 35 | " },\n" + 36 | " {\n" + 37 | " \"name\": \"test.fr\",\n" + 38 | " \"type\": \"MX\",\n" + 39 | " \"content\": \".*\",\n" + 40 | " \"ttl\": \".*\",\n" + 41 | " \"prio\": \".*\"\n" + 42 | " },\n" + 43 | " {\n" + 44 | " \"name\": \"test.fr\",\n" + 45 | " \"type\": \"NS\",\n" + 46 | " \"content\": \"^ns(101[6-9]|10[2-9][0-9]|11[0-1][0-9]|112[0-6]).ui-dns.(biz|com|org|de)$\",\n" + 47 | " \"ttl\": \".*\",\n" + 48 | " \"!.*\": \".*\"\n" + 49 | " },\n" + 50 | " {\n" + 51 | " \"name\": \"test.fr\",\n" + 52 | " \"type\": \"TXT\",\n" + 53 | " \"content\": \"\\\"zone-ownership-verification-[A-Fa-f0-9]{64}\\\"\",\n" + 54 | " \"ttl\": \".*\",\n" + 55 | " \"!.*\": \".*\"\n" + 56 | " },\n" + 57 | " \"!.*\"" + 58 | " ]\n" + 59 | " }"; 60 | String actual = 61 | "{\n" + 62 | " \"records\" : [ {\n" + 63 | " \"name\" : \"autodiscover.test.fr\",\n" + 64 | " \"type\" : \"CNAME\",\n" + 65 | " \"content\" : \"adsredir.1and1.info\",\n" + 66 | " \"ttl\" : 3600\n" + 67 | " },{\n" + 68 | " \"name\" : \"test.fr\",\n" + 69 | " \"type\" : \"MX\",\n" + 70 | " \"content\" : \"mx00.kundenserver.de\",\n" + 71 | " \"ttl\" : 3600,\n" + 72 | " \"prio\" : 10,\n" + 73 | " \"data\" : 10\n" + 74 | " }, {\n" + 75 | " \"name\" : \"test.fr\",\n" + 76 | " \"type\" : \"MX\",\n" + 77 | " \"content\" : \"mx01.kundenserver.de\",\n" + 78 | " \"ttl\" : 3600,\n" + 79 | " \"prio\" : 10\n" + 80 | " }," + 81 | " {\n" + 82 | " \"lol\": \"_domainconnect.#[domainName]\",\n" + 83 | " \"abc\": \"CNAME\",\n" + 84 | " \"ttl\": \".*\"\n" + 85 | " }, {\n" + 86 | " \"name\" : \"test.fr\",\n" + 87 | " \"type\" : \"NS\",\n" + 88 | " \"content\" : \"ns1094.ui-dns.com\",\n" + 89 | " \"ttl\" : 86400\n" + 90 | " },{\n" + 91 | " \"name\" : \"test.fr\",\n" + 92 | " \"type\" : \"TXT\",\n" + 93 | " \"content\" : \"\\\"zone-ownership-verification-1f3084631d3f396d4df4b07df37c94d7cb8b569cfa03a899f343381021fcf112\\\"\",\n" + 94 | " \"ttl\" : 60\n" + 95 | " }]}"; 96 | JSONCompare.assertMatches(expected, actual); 97 | } 98 | 99 | @Test 100 | public void testIssue1_negative() { 101 | String expected = 102 | "{\n" + 103 | " \"records\": [\n" + 104 | " {\n" + 105 | " \"name\": \"autodiscover.test.fr\",\n" + 106 | " \"type\": \"CNAME\",\n" + 107 | " \"content\": \".*\",\n" + 108 | " \"ttl\": \".*\",\n" + 109 | " \"!.*\": \".*\"\n" + 110 | " },\n" + 111 | " {\n" + 112 | " \"!name\": \".*\",\n" + 113 | " \"!type\": \".*\",\n" + 114 | " \"!content\": \".*\",\n" + 115 | " \"!ttl\": \".*\"" + 116 | " }," + 117 | " {\n" + 118 | " \"name\": \"test.fr\",\n" + 119 | " \"type\": \"MX\",\n" + 120 | " \"content\": \".*\",\n" + 121 | " \"ttl\": \".*\",\n" + 122 | " \"prio\": \".*\",\n" + 123 | " \"!.*\": \".*\"\n" + 124 | " },\n" + 125 | " {\n" + 126 | " \"name\": \"test.fr\",\n" + 127 | " \"type\": \"MX\",\n" + 128 | " \"content\": \".*\",\n" + 129 | " \"ttl\": \".*\",\n" + 130 | " \"prio\": \".*\"\n" + 131 | " },\n" + 132 | " {\n" + 133 | " \"name\": \"test.fr\",\n" + 134 | " \"type\": \"NS\",\n" + 135 | " \"content\": \"^ns(101[6-9]|10[2-9][0-9]|11[0-1][0-9]|112[0-6]).ui-dns.(biz|com|org|de)$\",\n" + 136 | " \"ttl\": \".*\",\n" + 137 | " \"!.*\": \".*\"\n" + 138 | " },\n" + 139 | " {\n" + 140 | " \"name\": \"test.fr\",\n" + 141 | " \"type\": \"TXT\",\n" + 142 | " \"content\": \"\\\"zone-ownership-verification-[A-Fa-f0-9]{64}\\\"\",\n" + 143 | " \"ttl\": \".*\",\n" + 144 | " \"!.*\": \".*\"\n" + 145 | " },\n" + 146 | " \"!.*\"" + 147 | " ]\n" + 148 | " }"; 149 | String actual = 150 | "{\n" + 151 | " \"records\" : [ {\n" + 152 | " \"name\" : \"autodiscover.test.fr\",\n" + 153 | " \"type\" : \"CNAME\",\n" + 154 | " \"content\" : \"adsredir.1and1.info\",\n" + 155 | " \"ttl\" : 3600\n" + 156 | " },{\n" + 157 | " \"name\" : \"test.fr\",\n" + 158 | " \"type\" : \"MX\",\n" + 159 | " \"content\" : \"mx00.kundenserver.de\",\n" + 160 | " \"ttl\" : 3600,\n" + 161 | " \"prio\" : 10,\n" + 162 | " \"data\" : 10\n" + 163 | " }, {\n" + 164 | " \"name\" : \"test.fr\",\n" + 165 | " \"type\" : \"MX\",\n" + 166 | " \"content\" : \"mx01.kundenserver.de\",\n" + 167 | " \"ttl\" : 3600,\n" + 168 | " \"prio\" : 10\n" + 169 | " }," + 170 | " {\n" + 171 | " \"lol\": \"_domainconnect.#[domainName]\",\n" + 172 | " \"abc\": \"CNAME\",\n" + 173 | " \"ttl\": \".*\"\n" + 174 | " }, {\n" + 175 | " \"name\" : \"test.fr\",\n" + 176 | " \"type\" : \"NS\",\n" + 177 | " \"content\" : \"ns1094.ui-dns.com\",\n" + 178 | " \"ttl\" : 86400\n" + 179 | " },{\n" + 180 | " \"name\" : \"test.fr\",\n" + 181 | " \"type\" : \"TXT\",\n" + 182 | " \"content\" : \"\\\"zone-ownership-verification-1f3084631d3f396d4df4b07df37c94d7cb8b569cfa03a899f343381021fcf112\\\"\",\n" + 183 | " \"ttl\" : 60\n" + 184 | " }]}"; 185 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue7Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.JSONCompare; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | 8 | public class Issue7Test { 9 | 10 | @Test 11 | public void matchUseCaseLiterals() { 12 | String expected = "{\"records\": [\".*\"]}"; 13 | String actual = "{\"records\": [\".*\" ]}"; 14 | JSONCompare.assertMatches(expected, actual); 15 | expected = "{\"records\": [\"\\\\!.*\"]}"; 16 | actual = "{\"records\": [\"!.*\" ]}"; 17 | JSONCompare.assertMatches(expected, actual); 18 | } 19 | 20 | @Test 21 | public void matchUseCaseLiterals_negative() { 22 | String expected = "{\"records\": [\"!.*\"]}"; 23 | String actual = "{\"records\": [\"!.*\" ]}"; 24 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 25 | } 26 | 27 | @Test 28 | public void checkJsonArrayHasNoExtraElements() { 29 | String expected = "{\"records\": [\"!.*\"]}"; 30 | String actual = "{\"records\": []}"; 31 | JSONCompare.assertMatches(expected, actual); 32 | } 33 | 34 | @Test 35 | public void checkJsonArrayHasNoExtraElements1() { 36 | String expected = "{\"records\": [\"!.*\"]}"; 37 | String actual = "{\"records\": [{\"a\":0}]}"; 38 | JSONCompare.assertNotMatches(expected, actual); 39 | } 40 | 41 | @Test 42 | public void checkJsonArrayHasExtraElements() { 43 | String expected = "{\"records\": [\".*\"]}"; 44 | String actual = "{\"records\": [1,2]}"; 45 | JSONCompare.assertMatches(expected, actual); 46 | } 47 | 48 | @Test 49 | public void checkJsonArrayHasExtraElements1() { 50 | String expected = "{\"records\": [\".*\"]}"; 51 | String actual = "{\"records\": [{\"a\":0}]}"; 52 | JSONCompare.assertMatches(expected, actual); 53 | } 54 | 55 | @Test 56 | public void checkJsonArrayHasExtraElements2() { 57 | String expected = 58 | "{\"records\":[\".*\"]}"; 59 | String actual = "{\"records\":[{\"a\":0}]}"; 60 | JSONCompare.assertMatches(expected, actual); 61 | } 62 | 63 | @Test 64 | public void checkJsonArrayHasExtraElements3() { 65 | String expected = 66 | "{\"records\":[\".+\"]}"; 67 | String actual = "{\"records\":[false]}"; 68 | JSONCompare.assertMatches(expected, actual); 69 | } 70 | 71 | @Test 72 | public void checkJsonArrayHasExtraElements4() { 73 | String expected = "{\"records\": [[1], false, \".*\"]}"; 74 | String actual = "{\"records\": [[1], {\"a\":0}, false]}"; 75 | JSONCompare.assertMatches(expected, actual); 76 | } 77 | 78 | @Test 79 | public void checkJsonArrayHasExtraElements4_negative() { 80 | String expected = "{\"records\": [{\"a\":0}, [1], false, \".+\"]}"; 81 | String actual = "{\"records\": [[1], {\"a\":0}, false]}"; 82 | JSONCompare.assertNotMatches(expected, actual); 83 | } 84 | 85 | @Test 86 | public void checkJsonArrayHasNoExtraElements3_negative() { 87 | String expected = 88 | "{\"records\":[\".+\"]}"; 89 | String actual = "{\"records\":[]}"; 90 | JSONCompare.assertNotMatches(expected, actual); 91 | } 92 | 93 | @Test 94 | public void checkJsonArrayHasNoTextElementThatMatchesTheExpectedTextValue() { 95 | String expected = "{\"records\": [\"!b\"]}"; 96 | String actual = "{\"records\": [\"c\"]}"; 97 | JSONCompare.assertMatches(expected, actual); 98 | } 99 | 100 | @Test 101 | public void checkJsonArrayHasNoTextElementThatMatchesTheExpectedTextValue_negative() { 102 | String expected = "{\"records\": [\"!b\"]}"; 103 | String actual = "{\"records\": [\"b\"]}"; 104 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 105 | } 106 | 107 | @Test 108 | public void checkJsonArrayHasJsonObjectElementThatMatchesTheExpectedTextValue() { 109 | String expected = "{\"records\": [{\".*\": \".*\"}]}"; 110 | String actual = "{\"records\": [{\"a\": 0}]}"; 111 | JSONCompare.assertMatches(expected, actual); 112 | } 113 | 114 | @Test 115 | public void checkJsonArrayHasNoJsonObjectElementThatMatchesTheExpectedTextValue_negative1() { 116 | String expected = "{\"records\": [{\"!.*\": \".*\"}]}"; 117 | String actual = "{\"records\": [\"a\", [true]]}"; 118 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 119 | } 120 | 121 | @Test 122 | public void checkJsonArrayHasAJsonObjectElementThatMatchesTheExpectedJsonObjectValue() { 123 | String expected = "{\"records\": [{\"!.*\": \".*\"}]}"; 124 | String actual = "{\"records\": [{\"a\":0}]}"; 125 | JSONCompare.assertNotMatches(expected, actual); 126 | } 127 | 128 | @Test 129 | public void checkJsonArrayHasAJsonObjectElementThatMatchesTheExpectedJsonObjectValue1() { 130 | String expected = "{\"records\": [{\"!.*\": \".*\"}]}"; 131 | String actual = "{\"records\": [\"test\"]}"; 132 | JSONCompare.assertNotMatches(expected, actual); 133 | } 134 | 135 | @Test 136 | public void checkJsonArrayHasNoJsonObjectElementThatMatchesTheExpectedTextValue3() { 137 | String expected = "{\"records\": [{\"a\": \"!b\"}]}"; 138 | String actual = "{\"records\": [{\"a\": \"c\"}]}"; 139 | JSONCompare.assertMatches(expected, actual); 140 | } 141 | 142 | @Test 143 | public void checkJsonArrayHasNoJsonObjectElementThatMatchesTheExpectedTextValue4() { 144 | String expected = "{\"records\": [{\"a\": \"!b\"}]}"; 145 | String actual = "{\"records\": [{\"b\": \"c\"}]}"; 146 | JSONCompare.assertNotMatches(expected, actual); 147 | } 148 | 149 | @Test 150 | public void checkJsonArrayHasNoJsonObjectElementThatMatchesTheExpectedTextValue5() { 151 | String expected = "{\"records\": [{\"!a\": \"b\"}]}"; 152 | String actual = "{\"records\": [{\"x\": \"b\"}]}"; 153 | JSONCompare.assertMatches(expected, actual); 154 | } 155 | 156 | @Test 157 | public void checkJsonArrayHasNoJsonObjectElementThatMatchesTheExpectedTextValue6() { 158 | String expected = "{\"records\": [{\"!a\": \"b\"}]}"; 159 | String actual = "{\"records\": [{\"x\": \"c\"}]}"; 160 | JSONCompare.assertMatches(expected, actual); 161 | } 162 | 163 | @Test 164 | public void checkJsonObjectDoNotFindKeyHasValueMatch() { 165 | String expected = "{\"!a\": \"b\"}"; 166 | String actual = "{\"x\": \"b\"}"; 167 | JSONCompare.assertMatches(expected, actual); 168 | } 169 | 170 | @Test 171 | public void checkJsonObjectDoNotFindKeyHasValueMatch1() { 172 | String expected = "{\"!a\": \"b\"}"; 173 | String actual = "{\"x\": \"c\"}"; 174 | JSONCompare.assertMatches(expected, actual); 175 | } 176 | 177 | @Test 178 | public void checkJsonArrayHasNoJsonObjectElementThatMatchesTheExpectedTextValue3_negative() { 179 | String expected = "{\"records\": [{\"a\": \"!b\"}]}"; 180 | String actual = "{\"records\": [{\"a\": \"b\"}]}"; 181 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 182 | } 183 | 184 | @Test 185 | public void checkJsonArrayHasNoTextElementThatMatchesTheExpectedTextValue1() { 186 | String expected = "{\"records\": [\"!b\"]}"; 187 | String actual = "{\"records\": [{\"a\": 0}, \"c\"]}"; 188 | JSONCompare.assertMatches(expected, actual); 189 | } 190 | 191 | @Test 192 | public void checkJsonArrayHasNoJsonObjectElementThatMatchesTheExpectedTextValue_negative() { 193 | String expected = "{\"records\": [\"!b\"]}"; 194 | String actual = "{\"records\": [{\"a\": 0}, \"b\"]}"; 195 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 196 | } 197 | 198 | @Test 199 | public void checkJsonArrayHasNoElementThatMatchesTheExpectedJsonObject1() { 200 | String expected = "{\"records\": [ {\"!a\": 0} ]}"; 201 | String actual = "{\"records\": [ \"b\" ]}"; 202 | JSONCompare.assertNotMatches(expected, actual); 203 | } 204 | 205 | @Test 206 | public void checkJsonArrayHasNoElementThatMatchesTheExpectedJsonObject_negative() { 207 | String expected = "{\"records\": [ {\"!a\": 0} ]}"; 208 | String actual = "{\"records\": [ \"b\", {\"a\": 0} ]}"; 209 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 210 | } 211 | 212 | 213 | @Test 214 | public void checkJsonArrayHasNoElementThatMatchesTheExpectedJsonArray() { 215 | String expected = "{\"records\": [ [\"!a\"] ]}"; 216 | String actual = "{\"records\": [ \"b\" ]}"; 217 | JSONCompare.assertNotMatches(expected, actual); 218 | } 219 | 220 | @Test 221 | public void checkJsonArrayHasNoJsonArrayElementThatMatchesTheExpectedTextValue() { 222 | String expected = "{\"records\": [ [\"!c\"] ]}"; 223 | String actual = "{\"records\": [ [\"b\"] ]}"; 224 | JSONCompare.assertMatches(expected, actual); 225 | } 226 | 227 | @Test 228 | public void checkJsonArrayHasNoJsonArrayElementThatMatchesTheExpectedTextValue_negative() { 229 | String expected = "{\"records\": [\"!t.*\"]}"; 230 | String actual = "{\"records\": [ [\"b\"], true ]}"; 231 | assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 232 | } 233 | 234 | @Test 235 | public void checkJsonArrayHasNoElementThatMatchesTheExpectedJsonObjectValue() { 236 | String expected = "{\"records\": [ {\"!b\":\"0\"} ]}"; 237 | String actual = "{\"records\": [{\"a\":\"0\"}]}"; 238 | JSONCompare.assertMatches(expected, actual); 239 | } 240 | 241 | @Test 242 | public void checkJsonArrayHasNoJsonObjectElementThatMatchesTheExpectedJsonObjectValue_negative() { 243 | String expected = "{\"records\": [ {\"!b\":\"0\"} ]}"; 244 | String actual = "{\"records\": [{\"a\":\"0\"}, [true], {\"b\":\"0\"} ]}"; 245 | JSONCompare.assertMatches(expected, actual); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | JSON Compare 6 | Java library for comparing JSONs 7 | http://github.com/fslev/json-compare 8 | 9 | 10 | Florin Slevoaca 11 | slevoaca.florin@gmail.com 12 | EEST 13 | 14 | 15 | 16 | 17 | UTF-8 18 | 2.20.1 19 | 2.10.0 20 | 6.0.1 21 | 22 | 23 | com.github.fslev 24 | json-compare 25 | 7.3-SNAPSHOT 26 | 27 | 28 | 29 | 30 | com.fasterxml.jackson 31 | jackson-bom 32 | ${jackson.version} 33 | import 34 | pom 35 | 36 | 37 | org.junit 38 | junit-bom 39 | ${junit.version} 40 | pom 41 | import 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | com.fasterxml.jackson.core 50 | jackson-databind 51 | 52 | 53 | com.fasterxml.jackson.core 54 | jackson-core 55 | 56 | 57 | 58 | com.jayway.jsonpath 59 | json-path 60 | ${json.path.version} 61 | 62 | 63 | org.junit.jupiter 64 | junit-jupiter-api 65 | 66 | 67 | 68 | 69 | 70 | nexus-upload 71 | 72 | false 73 | 74 | 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-source-plugin 79 | 3.4.0 80 | 81 | 82 | attach-sources 83 | 84 | jar-no-fork 85 | 86 | 87 | 88 | 89 | 90 | org.apache.maven.plugins 91 | maven-javadoc-plugin 92 | 3.12.0 93 | 94 | 95 | attach-javadocs 96 | 97 | jar 98 | 99 | 100 | 101 | 102 | 11 103 | none 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-gpg-plugin 109 | 3.2.8 110 | 111 | 112 | sign-artifacts 113 | verify 114 | 115 | sign 116 | 117 | 118 | 119 | 120 | 121 | org.sonatype.central 122 | central-publishing-maven-plugin 123 | 0.9.0 124 | true 125 | 126 | central 127 | true 128 | 129 | 130 | 131 | 132 | 133 | 134 | coveralls 135 | 136 | false 137 | 138 | 139 | 140 | 141 | org.eluder.coveralls 142 | coveralls-maven-plugin 143 | 4.3.0 144 | 146 | 147 | 148 | javax.xml.bind 149 | jaxb-api 150 | 2.3.1 151 | 152 | 153 | 154 | 155 | ${COVERALLS_TOKEN} 156 | 157 | 158 | 159 | org.jacoco 160 | jacoco-maven-plugin 161 | 0.8.13 162 | 163 | 164 | 165 | prepare-agent 166 | 167 | 168 | 169 | 170 | report 171 | test 172 | 173 | report 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | org.apache.maven.plugins 187 | maven-surefire-plugin 188 | 3.5.4 189 | 190 | 191 | 192 | junit.jupiter.execution.parallel.enabled = true 193 | junit.jupiter.execution.parallel.mode.default = concurrent 194 | 195 | 196 | 197 | 198 | 199 | org.apache.maven.plugins 200 | maven-compiler-plugin 201 | 3.14.1 202 | 203 | 8 204 | 17 205 | 206 | 207 | 208 | org.apache.felix 209 | maven-bundle-plugin 210 | true 211 | 6.0.0 212 | 213 | 214 | ${project.artifactId} 215 | ${project.groupId}.${project.artifactId} 216 | ${project.description} 217 | * 218 | 219 | 220 | <_removeheaders>Bnd-LastModified 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | scm:git:ssh://git@github.com/fslev/json-compare.git 229 | scm:git:ssh://git@github.com/fslev/json-compare.git 230 | https://github.com/fslev/json-compare/tree/main 231 | HEAD 232 | 233 | 234 | 235 | 236 | Apache License, Version 2.0 237 | http://www.apache.org/licenses/LICENSE-2.0.txt 238 | repo 239 | 240 | 241 | 242 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/diffs/JsonArrayDiffTests.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.diffs; 2 | 3 | import io.json.compare.CompareMode; 4 | import io.json.compare.JSONCompare; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.HashSet; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertThrows; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | class JsonArrayDiffTests { 14 | 15 | @Test 16 | void compareJsonArraysAndCheckForFor1ElementNotFoundDifference() { 17 | String expected = "[\"a\",\"c\",1,2,true,false,12.091,null]"; 18 | String actual = "[\"a\",\"b\",1,2,true,false,12.091,null]"; 19 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 20 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*\\Q$[1]\\E was not found.*\"c\".*")); 21 | JSONCompare.assertNotMatches(expected, actual); 22 | } 23 | 24 | @Test 25 | void compareJsonArraysAndCheckForMultipleElementNotFoundDifferences() { 26 | String expected = "[\"a\",\"c\",1,200,true,false,12.092,null,{\"lorem\":\"ipsum\"}]"; 27 | String actual = "[12.091,10,\"b\",1,\"a\",2,true,{\"lorem\":\"ipsum-updated\"},\"some text\",false]"; 28 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 29 | assertTrue(error.getMessage().matches("(?s).*FOUND 5 DIFFERENCE.*" + 30 | "\\Q$[1]\\E was not found.*\"c\".*" + 31 | "\\Q$[3]\\E was not found.*200.*" + 32 | "\\Q$[6]\\E was not found.*12.092.*" + 33 | "\\Q$[7]\\E was not found.*null.*" + 34 | "\\Q$[8]\\E was not found.*\\{.*\"lorem\".*\"ipsum\".*}.*")); 35 | JSONCompare.assertNotMatches(expected, actual); 36 | } 37 | 38 | @Test 39 | void compareJsonArraysAndCheckForOneMatchAnyDifferences() { 40 | String expected = "[\"a\",\".*\",1,\".*\",\".*\"]"; 41 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 42 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 43 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 44 | "\\Q$[4]\\E -> Expected condition \"\\Q.*\\E\" was not met. Actual JSON ARRAY has no extra elements.*")); 45 | JSONCompare.assertNotMatches(expected, actual); 46 | } 47 | 48 | @Test 49 | void compareJsonArraysAndCheckForMultipleMatchAnyDifferences() { 50 | String expected = "[\"a\",\".*\",1,\".*\",\".*\",\".*\"]"; 51 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 52 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 53 | assertTrue(error.getMessage().matches("(?s).*FOUND 2 DIFFERENCE.*" + 54 | "\\Q$[4]\\E -> Expected condition \"\\Q.*\\E\" was not met. Actual JSON ARRAY has no extra elements.*" + 55 | "\\Q$[5]\\E -> Expected condition \"\\Q.*\\E\" was not met. Actual JSON ARRAY has no extra elements.*")); 56 | JSONCompare.assertNotMatches(expected, actual); 57 | 58 | // empty actual json array 59 | String expected1 = "[\"a\",\".*\",1,\".*test\",\".*\",\".*\"]"; 60 | String actual1 = "[]"; 61 | AssertionError error1 = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected1, actual1)); 62 | assertTrue(error1.getMessage().matches("(?s).*FOUND 6 DIFFERENCE.*" + 63 | "\\Q$[0]\\E was not found.*\"a\".*" + 64 | "\\Q$[1]\\E -> Expected condition \".*\" was not met. Actual JSON ARRAY has no extra elements.*" + 65 | "\\Q$[2]\\E was not found.*1.*" + 66 | "\\Q$[3]\\E was not found.*\"\\Q.*test\\E\".*" + 67 | "\\Q$[4]\\E -> Expected condition \".*\" was not met. Actual JSON ARRAY has no extra elements.*" + 68 | "\\Q$[5]\\E -> Expected condition \".*\" was not met. Actual JSON ARRAY has no extra elements.*")); 69 | JSONCompare.assertNotMatches(expected1, actual1); 70 | } 71 | 72 | @Test 73 | void compareJsonArraysAndCheckForOneDoNotMatchDifferences() { 74 | String expected = "[1,\"!a\",true]"; 75 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 76 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 77 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 78 | "\\Q$[1]\\E was found.*\"!a\".*")); 79 | JSONCompare.assertNotMatches(expected, actual); 80 | } 81 | 82 | @Test 83 | void compareJsonArraysAndCheckForMultipleDoNotMatchDifferences() { 84 | String expected = "[{\"lorem\":\"ipsum\"},\"!1\",\"!a\",\"!true\"]"; 85 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 86 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 87 | assertTrue(error.getMessage().matches("(?s).*FOUND 3 DIFFERENCE.*" + 88 | "\\Q$[1]\\E was found.*\"!1\".*" + 89 | "\\Q$[2]\\E was found.*\"!a\".*" + 90 | "\\Q$[3]\\E was found.*\"!true\".*")); 91 | JSONCompare.assertNotMatches(expected, actual); 92 | } 93 | 94 | @Test 95 | void compareJsonArraysAndCheckForOneDoNotMatchAnyDifferences() { 96 | String expected = "[{\"lorem\":\"ipsum\"},\"1\",\"!.*\"]"; 97 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 98 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 99 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 100 | "\\Q$[2]\\E -> Expected condition \"\\Q!.*\\E\" was not met. Actual JSON ARRAY has extra elements.*")); 101 | JSONCompare.assertNotMatches(expected, actual); 102 | } 103 | 104 | @Test 105 | void compareJsonArraysAndCheckForMultipleDoNotMatchAnyDifferences() { 106 | String expected = "[{\"lorem\":\"ipsum\"},\"!.*\",\"1\",\"!.*\",\"!.*\"]"; 107 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 108 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual)); 109 | assertTrue(error.getMessage().matches("(?s).*FOUND 3 DIFFERENCE.*" + 110 | "\\Q$[1]\\E -> Expected condition \"\\Q!.*\\E\" was not met. Actual JSON ARRAY has extra elements.*" + 111 | "\\Q$[3]\\E -> Expected condition \"\\Q!.*\\E\" was not met. Actual JSON ARRAY has extra elements.*" + 112 | "\\Q$[4]\\E -> Expected condition \"\\Q!.*\\E\" was not met. Actual JSON ARRAY has extra elements.*")); 113 | JSONCompare.assertNotMatches(expected, actual); 114 | } 115 | 116 | @Test 117 | void compareJsonArraysAndCheckForJsonStrictOrderDifferences() { 118 | String expected = "[{\"lorem\":\"ipsum\"},\"!.*\",\"1\",{\"lorem2\":\"ipsum2\",\"lorem3\":\"ipsum3\"}]"; 119 | String actual = "[\"a\",true,1,{\"lorem2\":\"ipsum-updated\"}]"; 120 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_STRICT_ORDER)))); 121 | assertTrue(error.getMessage().matches("(?s).*FOUND 4 DIFFERENCE.*" + 122 | "\\Q$[0]\\E -> Different JSON types: expected ObjectNode but got TextNode.*" + 123 | "\\Q$[1]\\E -> Expected condition \"\\Q!.*\\E\" was not met. Actual JSON ARRAY has extra elements.*" + 124 | "\\Q$[3].lorem2\\E.*Expected value: \"ipsum2\" But got: \"ipsum-updated\".*" + 125 | "\\Q$[3].lorem3\\E was not found.*")); 126 | JSONCompare.assertNotMatches(expected, actual); 127 | } 128 | 129 | @Test 130 | void compareJsonArraysAndCheckForJsonNonExtensibleDifferences() { 131 | String expected = "[{\"lorem\":\"ipsum\"},\"a\"]"; 132 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 133 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE)))); 134 | assertTrue(error.getMessage().matches("(?s).*FOUND 1 DIFFERENCE.*" + 135 | "Actual JSON ARRAY has extra elements.*")); 136 | JSONCompare.assertNotMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE))); 137 | } 138 | 139 | @Test 140 | void compareJsonArraysAndCheckForJsonNonExtensibleAndOtherDifferences() { 141 | String expected = "[{\"lorem\":\"ipsum\"},\"c\",\"!1\",true]"; 142 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"}]"; 143 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE)))); 144 | assertTrue(error.getMessage().matches("(?s).*FOUND 3 DIFFERENCE.*" + 145 | "\\Q$[1]\\E was not found.*\"c\".*" + 146 | "\\Q$[2]\\E was found.*\"!1\".*" + 147 | "\\Q$\\E -> Actual JSON ARRAY has extra elements.*")); 148 | JSONCompare.assertNotMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE))); 149 | 150 | String expected1 = "[{\"lorem\":\"ipsum\"},\"c\",\"!1\",true]"; 151 | String actual1 = "[\"a\",true,1,{\"lorem\":\"ipsum\"},-10.02]"; 152 | AssertionError error1 = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected1, actual1, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE)))); 153 | assertTrue(error1.getMessage().matches("(?s).*FOUND 3 DIFFERENCE.*" + 154 | "\\Q$[1]\\E was not found.*\"c\".*" + 155 | "\\Q$[2]\\E was found.*\"!1\".*" + 156 | "\\Q$\\E -> Actual JSON ARRAY has extra elements.*")); 157 | JSONCompare.assertNotMatches(expected1, actual1, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE))); 158 | } 159 | 160 | @Test 161 | void compareJsonArraysAndCheckForJsonNonExtensibleAndAndJsonStrictOrderDifferences() { 162 | String expected = "[{\"lorem\":\"ipsum\"},\"c\",\"!1\",true]"; 163 | String actual = "[\"a\",true,1,{\"lorem\":\"ipsum\"},-10.02]"; 164 | AssertionError error = assertThrows(AssertionError.class, () -> JSONCompare.assertMatches(expected, actual, 165 | new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE, CompareMode.JSON_ARRAY_STRICT_ORDER)))); 166 | assertTrue(error.getMessage().matches("(?s).*FOUND 5 DIFFERENCE.*" + 167 | "\\Q$[0]\\E -> Different JSON types: expected ObjectNode but got TextNode.*" + 168 | "\\Q$[1]\\E.*Expected value: \"c\" But got: true.*" + 169 | "\\Q$[2]\\E was found.*\"!1\".*" + 170 | "\\Q$[3]\\E.*Different JSON types: expected BooleanNode but got ObjectNode.*" + 171 | "\\Q$\\E -> Actual JSON ARRAY has extra elements.*")); 172 | JSONCompare.assertNotMatches(expected, actual, 173 | new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_NON_EXTENSIBLE, CompareMode.JSON_ARRAY_STRICT_ORDER))); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/test/java/io/json/compare/matcher/issues/Issue11Test.java: -------------------------------------------------------------------------------- 1 | package io.json.compare.matcher.issues; 2 | 3 | import io.json.compare.CompareMode; 4 | import io.json.compare.JSONCompare; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.HashSet; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | class Issue11Test { 13 | 14 | @Test 15 | void testSimpleJsonArrayStrictOrderWithRegexFieldsThrowsCorrectMessage() { 16 | String expected = "{\".*\":{\"eventLogs\":[{\"id\":2},{\"id\":4},{\"id\":1},{\"id\":3}]}}"; 17 | String actual = "{\"_embedded\":{\"eventLogs\":[{\"id\":1},{\"id\":2},{\"id\":3},{\"id\":4}]}}"; 18 | try { 19 | JSONCompare.assertMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_STRICT_ORDER))); 20 | } catch (AssertionError e) { 21 | assertTrue(e.getMessage().contains("$..*.eventLogs[0].id")); 22 | } 23 | } 24 | 25 | @Test 26 | void testJsonArrayStrictOrderThrowsCorrectMessage() { 27 | String expected = "{\"_embedded\":{\"eventLogs\":[{\"id\":2},{\"id\":4},{\"id\":1},{\"id\":3}]}}"; 28 | String actual = "{\n" + 29 | " \"_embedded\": {\n" + 30 | " \"eventLogs\": [\n" + 31 | " {\n" + 32 | " \"id\": 1,\n" + 33 | " \"session_id\": \"session id 1\",\n" + 34 | " \"date\": \"2019-10-01T10:40:16.000+0000\",\n" + 35 | " \"entity\": \"USER\",\n" + 36 | " \"action\": \"CREATE\",\n" + 37 | " \"data\": {\n" + 38 | " \"some data for session id\": 1\n" + 39 | " },\n" + 40 | " \"outcome\": \"SUCCESS\",\n" + 41 | " \"message\": \"some message for session 1\",\n" + 42 | " \"_links\": {\n" + 43 | " \"self\": {\n" + 44 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/1\"\n" + 45 | " },\n" + 46 | " \"eventLog\": {\n" + 47 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/1\"\n" + 48 | " }\n" + 49 | " }\n" + 50 | " },\n" + 51 | " {\n" + 52 | " \"id\": 2,\n" + 53 | " \"session_id\": \"session id 2\",\n" + 54 | " \"date\": \"2019-10-01T10:40:16.000+0000\",\n" + 55 | " \"entity\": \"GROUP\",\n" + 56 | " \"action\": \"UPDATE\",\n" + 57 | " \"data\": {\n" + 58 | " \"some data for session id\": 2\n" + 59 | " },\n" + 60 | " \"outcome\": \"SUCCESS\",\n" + 61 | " \"message\": \"some message for session 2\",\n" + 62 | " \"_links\": {\n" + 63 | " \"self\": {\n" + 64 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/2\"\n" + 65 | " },\n" + 66 | " \"eventLog\": {\n" + 67 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/2\"\n" + 68 | " }\n" + 69 | " }\n" + 70 | " },\n" + 71 | " {\n" + 72 | " \"id\": 3,\n" + 73 | " \"session_id\": \"session id 2\",\n" + 74 | " \"date\": \"2019-10-02T10:40:16.000+0000\",\n" + 75 | " \"entity\": \"USER\",\n" + 76 | " \"action\": \"REMOVE\",\n" + 77 | " \"data\": {\n" + 78 | " \"some other data for session id\": 2\n" + 79 | " },\n" + 80 | " \"outcome\": \"SUCCESS\",\n" + 81 | " \"message\": \"some other for session 2\",\n" + 82 | " \"_links\": {\n" + 83 | " \"self\": {\n" + 84 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/3\"\n" + 85 | " },\n" + 86 | " \"eventLog\": {\n" + 87 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/3\"\n" + 88 | " }\n" + 89 | " }\n" + 90 | " },\n" + 91 | " {\n" + 92 | " \"id\": 4,\n" + 93 | " \"session_id\": \"session id 3\",\n" + 94 | " \"date\": \"2019-10-03T10:40:16.000+0000\",\n" + 95 | " \"entity\": \"GROUP\",\n" + 96 | " \"action\": \"ADD_TO_GROUP\",\n" + 97 | " \"data\": {\n" + 98 | " \"some data for session id\": 3\n" + 99 | " },\n" + 100 | " \"outcome\": \"SUCCESS\",\n" + 101 | " \"message\": \"some message for session 3\",\n" + 102 | " \"_links\": {\n" + 103 | " \"self\": {\n" + 104 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/4\"\n" + 105 | " },\n" + 106 | " \"eventLog\": {\n" + 107 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/4\"\n" + 108 | " }\n" + 109 | " }\n" + 110 | " }\n" + 111 | " ]\n" + 112 | " },\n" + 113 | " \"_links\": {\n" + 114 | " \"self\": {\n" + 115 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/search/by?start=2019-10-01T00%3A00%3A00.000-00%3A00&end=2019-10-05T23%3A59%3A59.000-00%3A00&page=0&size=20&sort=date,asc\"\n" + 116 | " }\n" + 117 | " },\n" + 118 | " \"page\": {\n" + 119 | " \"size\": 20,\n" + 120 | " \"totalElements\": 4,\n" + 121 | " \"totalPages\": 1,\n" + 122 | " \"number\": 0\n" + 123 | " }\n" + 124 | "}"; 125 | try { 126 | JSONCompare.assertMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_STRICT_ORDER))); 127 | } catch (AssertionError e) { 128 | assertTrue(e.getMessage().contains("$._embedded.eventLogs[0].id")); 129 | } 130 | } 131 | 132 | @Test 133 | void testJsonArrayStrictOrderWithRegexFieldsThrowsCorrectMessage() { 134 | String expected = "{\".*\":{\"eventLogs\":[{\"id\":2},{\"id\":4},{\"id\":1},{\"id\":3}]}}"; 135 | String actual = "{\n" + 136 | " \"_embedded\": {\n" + 137 | " \"eventLogs\": [\n" + 138 | " {\n" + 139 | " \"id\": 1,\n" + 140 | " \"session_id\": \"session id 1\",\n" + 141 | " \"date\": \"2019-10-01T10:40:16.000+0000\",\n" + 142 | " \"entity\": \"USER\",\n" + 143 | " \"action\": \"CREATE\",\n" + 144 | " \"data\": {\n" + 145 | " \"some data for session id\": 1\n" + 146 | " },\n" + 147 | " \"outcome\": \"SUCCESS\",\n" + 148 | " \"message\": \"some message for session 1\",\n" + 149 | " \"_links\": {\n" + 150 | " \"self\": {\n" + 151 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/1\"\n" + 152 | " },\n" + 153 | " \"eventLog\": {\n" + 154 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/1\"\n" + 155 | " }\n" + 156 | " }\n" + 157 | " },\n" + 158 | " {\n" + 159 | " \"id\": 2,\n" + 160 | " \"session_id\": \"session id 2\",\n" + 161 | " \"date\": \"2019-10-01T10:40:16.000+0000\",\n" + 162 | " \"entity\": \"GROUP\",\n" + 163 | " \"action\": \"UPDATE\",\n" + 164 | " \"data\": {\n" + 165 | " \"some data for session id\": 2\n" + 166 | " },\n" + 167 | " \"outcome\": \"SUCCESS\",\n" + 168 | " \"message\": \"some message for session 2\",\n" + 169 | " \"_links\": {\n" + 170 | " \"self\": {\n" + 171 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/2\"\n" + 172 | " },\n" + 173 | " \"eventLog\": {\n" + 174 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/2\"\n" + 175 | " }\n" + 176 | " }\n" + 177 | " },\n" + 178 | " {\n" + 179 | " \"id\": 3,\n" + 180 | " \"session_id\": \"session id 2\",\n" + 181 | " \"date\": \"2019-10-02T10:40:16.000+0000\",\n" + 182 | " \"entity\": \"USER\",\n" + 183 | " \"action\": \"REMOVE\",\n" + 184 | " \"data\": {\n" + 185 | " \"some other data for session id\": 2\n" + 186 | " },\n" + 187 | " \"outcome\": \"SUCCESS\",\n" + 188 | " \"message\": \"some other for session 2\",\n" + 189 | " \"_links\": {\n" + 190 | " \"self\": {\n" + 191 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/3\"\n" + 192 | " },\n" + 193 | " \"eventLog\": {\n" + 194 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/3\"\n" + 195 | " }\n" + 196 | " }\n" + 197 | " },\n" + 198 | " {\n" + 199 | " \"id\": 4,\n" + 200 | " \"session_id\": \"session id 3\",\n" + 201 | " \"date\": \"2019-10-03T10:40:16.000+0000\",\n" + 202 | " \"entity\": \"GROUP\",\n" + 203 | " \"action\": \"ADD_TO_GROUP\",\n" + 204 | " \"data\": {\n" + 205 | " \"some data for session id\": 3\n" + 206 | " },\n" + 207 | " \"outcome\": \"SUCCESS\",\n" + 208 | " \"message\": \"some message for session 3\",\n" + 209 | " \"_links\": {\n" + 210 | " \"self\": {\n" + 211 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/4\"\n" + 212 | " },\n" + 213 | " \"eventLog\": {\n" + 214 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/4\"\n" + 215 | " }\n" + 216 | " }\n" + 217 | " }\n" + 218 | " ]\n" + 219 | " },\n" + 220 | " \"_links\": {\n" + 221 | " \"self\": {\n" + 222 | " \"href\": \"http://clouddashboardqa01.ops.server.lan:38351/event/search/by?start=2019-10-01T00%3A00%3A00.000-00%3A00&end=2019-10-05T23%3A59%3A59.000-00%3A00&page=0&size=20&sort=date,asc\"\n" + 223 | " }\n" + 224 | " },\n" + 225 | " \"page\": {\n" + 226 | " \"size\": 20,\n" + 227 | " \"totalElements\": 4,\n" + 228 | " \"totalPages\": 1,\n" + 229 | " \"number\": 0\n" + 230 | " }\n" + 231 | "}"; 232 | try { 233 | JSONCompare.assertMatches(expected, actual, new HashSet<>(Arrays.asList(CompareMode.JSON_ARRAY_STRICT_ORDER))); 234 | } catch (AssertionError e) { 235 | assertTrue(e.getMessage().contains("was not found")); 236 | } 237 | } 238 | } 239 | --------------------------------------------------------------------------------