From c31db004f93aade7756336a3dad497143e79c0f7 Mon Sep 17 00:00:00 2001 From: Thomas Deblock Date: Sun, 4 Jan 2026 19:00:37 +0100 Subject: [PATCH 1/3] feat: allow ignore properties --- .../matcher/CompositeJsonMatcher.java | 5 +- .../matcher/IgnoringFieldMatcher.java | 32 ++++ .../LenientJsonArrayPartialMatcher.java | 2 +- .../LenientJsonObjectPartialMatcher.java | 2 +- .../LenientNumberPrimitivePartialMatcher.java | 2 +- .../matcher/NullEqualsEmptyArrayMatcher.java | 2 +- .../jsondiff/matcher/PartialJsonMatcher.java | 2 +- .../deblock/jsondiff/matcher/PathMatcher.java | 178 ++++++++++++++++++ .../StrictJsonArrayPartialMatcher.java | 9 +- .../StrictJsonObjectPartialMatcher.java | 2 +- .../StrictPrimitivePartialMatcher.java | 2 +- .../IgnoringFieldsIntegrationTest.java | 33 ++++ .../matcher/CompositeJsonMatcherTest.java | 24 +-- ...ientNumberPrimitivePartialMatcherTest.java | 10 +- .../NullEqualsEmptyArrayMatcherTest.java | 12 +- 15 files changed, 274 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java create mode 100644 src/main/java/com/deblock/jsondiff/matcher/PathMatcher.java create mode 100644 src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java diff --git a/src/main/java/com/deblock/jsondiff/matcher/CompositeJsonMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/CompositeJsonMatcher.java index e8d83ff..9ffd393 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/CompositeJsonMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/CompositeJsonMatcher.java @@ -2,9 +2,6 @@ import com.deblock.jsondiff.diff.*; import tools.jackson.databind.JsonNode; -import tools.jackson.databind.node.ArrayNode; -import tools.jackson.databind.node.ObjectNode; -import tools.jackson.databind.node.ValueNode; import java.util.ArrayList; import java.util.Arrays; @@ -21,7 +18,7 @@ public CompositeJsonMatcher(PartialJsonMatcher ...jsonArrayPartialMatcher) { @Override public JsonDiff diff(Path path, JsonNode expected, JsonNode received) { return this.matchers.stream() - .filter(matcher -> matcher.manage(expected, received)) + .filter(matcher -> matcher.manage(path, received, expected)) .findFirst() .map(matcher -> matcher.jsonDiff(path, expected, received, this)) .orElseGet(() -> new UnMatchedPrimaryDiff(path, expected, received)); diff --git a/src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java new file mode 100644 index 0000000..ec7675a --- /dev/null +++ b/src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java @@ -0,0 +1,32 @@ +package com.deblock.jsondiff.matcher; + +import com.deblock.jsondiff.diff.JsonDiff; +import com.deblock.jsondiff.diff.MatchedPrimaryDiff; +import tools.jackson.databind.JsonNode; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class IgnoringFieldMatcher implements PartialJsonMatcher { + private final List fieldsToIgnore; + + public IgnoringFieldMatcher(List patterns) { + this.fieldsToIgnore = patterns; + } + + public IgnoringFieldMatcher(Pattern... patterns) { + this.fieldsToIgnore = Arrays.stream(patterns).toList(); + } + + @Override + public JsonDiff jsonDiff(Path path, JsonNode expectedJson, JsonNode receivedJson, JsonMatcher jsonMatcher) { + return new MatchedPrimaryDiff(path, expectedJson); + } + + @Override + public boolean manage(Path path, JsonNode expected, JsonNode received) { + String stringPath = path.toString(); + return fieldsToIgnore.stream().anyMatch(pattern -> pattern.matcher(stringPath).matches()); + } +} diff --git a/src/main/java/com/deblock/jsondiff/matcher/LenientJsonArrayPartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/LenientJsonArrayPartialMatcher.java index d3f5dab..88c828b 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/LenientJsonArrayPartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/LenientJsonArrayPartialMatcher.java @@ -58,7 +58,7 @@ public JsonDiff jsonDiff(Path path, ArrayNode expectedArrayNode, ArrayNode recie } @Override - public boolean manage(JsonNode expected, JsonNode received) { + public boolean manage(Path path, JsonNode received, JsonNode expected) { return expected.isArray() && received.isArray(); } diff --git a/src/main/java/com/deblock/jsondiff/matcher/LenientJsonObjectPartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/LenientJsonObjectPartialMatcher.java index b0b0d0f..86a92d8 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/LenientJsonObjectPartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/LenientJsonObjectPartialMatcher.java @@ -29,7 +29,7 @@ public JsonDiff jsonDiff(Path path, ObjectNode expectedJson, ObjectNode received } @Override - public boolean manage(JsonNode expected, JsonNode received) { + public boolean manage(Path path, JsonNode received, JsonNode expected) { return expected.isObject() && received.isObject(); } } \ No newline at end of file diff --git a/src/main/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcher.java index e471359..06c8298 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcher.java @@ -35,7 +35,7 @@ public JsonDiff jsonDiff(Path path, ValueNode expectedValue, ValueNode receivedV } @Override - public boolean manage(JsonNode expected, JsonNode received) { + public boolean manage(Path path, JsonNode received, JsonNode expected) { return expected.isNumber() && received.isNumber(); } } diff --git a/src/main/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcher.java index 35716c0..de73b5c 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcher.java @@ -19,7 +19,7 @@ public JsonDiff jsonDiff(Path path, JsonNode expectedJson, JsonNode receivedJson } @Override - public boolean manage(JsonNode expected, JsonNode received) { + public boolean manage(Path path, JsonNode received, JsonNode expected) { return (expected.isNull() && received.isArray()) || (received.isNull() && expected.isArray()); } diff --git a/src/main/java/com/deblock/jsondiff/matcher/PartialJsonMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/PartialJsonMatcher.java index 6d04e46..a9394ff 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/PartialJsonMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/PartialJsonMatcher.java @@ -6,6 +6,6 @@ public interface PartialJsonMatcher { JsonDiff jsonDiff(Path path, T expectedJson, T receivedJson, JsonMatcher jsonMatcher); - boolean manage(JsonNode expected, JsonNode received); + boolean manage(Path path, JsonNode received, JsonNode expected); } diff --git a/src/main/java/com/deblock/jsondiff/matcher/PathMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/PathMatcher.java new file mode 100644 index 0000000..151272a --- /dev/null +++ b/src/main/java/com/deblock/jsondiff/matcher/PathMatcher.java @@ -0,0 +1,178 @@ +package com.deblock.jsondiff.matcher; + + +public class PathMatcher { + public final PathMatcher.PathMatcherItem property; + public final PathMatcher next; + + public static PathMatcher from(String path) { + PathMatcher matcher = new PathMatcher(); + for (String part: path.split("\\.")) { + if (part.endsWith("]")) { + String index = part.substring(part.lastIndexOf("[") + 1, part.length() - 1); + matcher = matcher.add(new PathMatcherItem.ObjectProperty(part.substring(0, part.lastIndexOf("[")))); + if ("*".equals(index)) { + matcher = matcher.add(new PathMatcherItem.WilcardMatcherItem(Path.PathItem.ArrayIndex.class)); + } else { + matcher = matcher.add(new PathMatcherItem.ArrayIndex(Integer.parseInt(index))); + } + } else if ("*".equals(part)) { + matcher = matcher.add(new PathMatcherItem.WilcardMatcherItem(Path.PathItem.ObjectProperty.class)); + } else { + matcher = matcher.add(new PathMatcher.PathMatcherItem.ObjectProperty(part)); + } + } + return matcher; + } + + public PathMatcher() { + this(null, null); + } + + private PathMatcher(PathMatcher.PathMatcherItem property, PathMatcher next) { + this.property = property; + this.next = next; + } + + private PathMatcher(PathMatcher.PathMatcherItem property) { + this.property = property; + this.next = null; + } + + public PathMatcher add(PathMatcher.PathMatcherItem item) { + if (this.next == null) { + return new PathMatcher(this.property, new PathMatcher(item)); + } else { + return new PathMatcher(this.property, this.next.add(item)); + } + } + + public boolean match(Path path) { + int pathLength = length(path); + int matcherLength = length(); + + if (matcherLength > pathLength) { + return false; + } + + // Align path to match from the end + Path alignedPath = path; + for (int i = 0; i < pathLength - matcherLength; i++) { + alignedPath = alignedPath.next; + } + + return matchFromHere(alignedPath); + } + + private int length() { + int count = 0; + PathMatcher current = this; + while (current != null && current.property != null) { + count++; + current = current.next; + } + return count; + } + + private int length(Path path) { + int count = 0; + Path current = path; + while (current != null && current.property != null) { + count++; + current = current.next; + } + return count; + } + + private boolean matchFromHere(Path path) { + if (this.property == null) { + return next == null || next.matchFromHere(path); + } + if (path == null || path.property == null) { + return false; + } + if (this.property.match(path.property)) { + if (next == null) { + return path.next == null || path.next.property == null; + } + return path.next != null && next.matchFromHere(path.next); + } + return false; + } + + public String toString() { + return ((this.property == null) ? "$" : this.property) + + ((this.next == null) ? "" : "." + this.next); + } + + private interface PathMatcherItem { + static PathMatcherItem of(String property) { + return new PathMatcher.PathMatcherItem.ObjectProperty(property); + } + + static PathMatcherItem of(Integer index) { + return new PathMatcher.PathMatcherItem.ArrayIndex(index); + } + + boolean match(Path.PathItem pathItem); + + class WilcardMatcherItem implements PathMatcherItem { + private final Class type; + + public WilcardMatcherItem(Class type) { + this.type = type; + } + + public String toString() { + return "*"; + } + + @Override + public boolean match(Path.PathItem pathItem) { + return type.isAssignableFrom(pathItem.getClass()); + } + } + + class ArrayIndex implements PathMatcherItem { + public final int index; + + public ArrayIndex(int index) { + this.index = index; + } + + @Override + public String toString() { + return String.valueOf(index); + } + + @Override + public boolean match(Path.PathItem pathItem) { + if (pathItem instanceof Path.PathItem.ArrayIndex arrayIndex) { + return arrayIndex.index == this.index; + } + return false; + } + } + + class ObjectProperty implements PathMatcherItem { + public final String property; + + public ObjectProperty(String property) { + this.property = property; + } + + @Override + public String toString() { + return this.property; + } + + @Override + public boolean match(Path.PathItem pathItem) { + if (pathItem instanceof Path.PathItem.ObjectProperty objectProperty) { + return objectProperty.property.equals(this.property); + } + return false; + } + } + } +} diff --git a/src/main/java/com/deblock/jsondiff/matcher/StrictJsonArrayPartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/StrictJsonArrayPartialMatcher.java index 923786b..769fd81 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/StrictJsonArrayPartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/StrictJsonArrayPartialMatcher.java @@ -5,13 +5,6 @@ import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ArrayNode; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - public class StrictJsonArrayPartialMatcher implements PartialJsonMatcher { @Override public JsonDiff jsonDiff(Path path, ArrayNode expectedValues, ArrayNode receivedValues, JsonMatcher jsonMatcher) { @@ -38,7 +31,7 @@ public JsonDiff jsonDiff(Path path, ArrayNode expectedValues, ArrayNode received } @Override - public boolean manage(JsonNode expected, JsonNode received) { + public boolean manage(Path path, JsonNode received, JsonNode expected) { return expected.isArray() && received.isArray(); } } diff --git a/src/main/java/com/deblock/jsondiff/matcher/StrictJsonObjectPartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/StrictJsonObjectPartialMatcher.java index 0103bc9..89f4341 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/StrictJsonObjectPartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/StrictJsonObjectPartialMatcher.java @@ -40,7 +40,7 @@ public JsonDiff jsonDiff(Path path, ObjectNode expectedJson, ObjectNode received } @Override - public boolean manage(JsonNode expected, JsonNode received) { + public boolean manage(Path path, JsonNode received, JsonNode expected) { return expected.isObject() && received.isObject(); } } diff --git a/src/main/java/com/deblock/jsondiff/matcher/StrictPrimitivePartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/StrictPrimitivePartialMatcher.java index c6e4601..b2b4609 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/StrictPrimitivePartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/StrictPrimitivePartialMatcher.java @@ -20,7 +20,7 @@ public JsonDiff jsonDiff(Path path, ValueNode expectedValue, ValueNode receivedV } @Override - public boolean manage(JsonNode expected, JsonNode received) { + public boolean manage(Path path, JsonNode received, JsonNode expected) { return expected.isValueNode() && received.isValueNode(); } } diff --git a/src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java b/src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java new file mode 100644 index 0000000..c7674f6 --- /dev/null +++ b/src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java @@ -0,0 +1,33 @@ +package com.deblock.jsondiff.integration; + +import com.deblock.jsondiff.DiffGenerator; +import com.deblock.jsondiff.matcher.*; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class IgnoringFieldsIntegrationTest { + + private final CompositeJsonMatcher jsonMatcher = new CompositeJsonMatcher( + new IgnoringFieldMatcher(List.of( + Pattern.compile("\\$\\..*foo") + )), + new LenientJsonArrayPartialMatcher(), + new LenientJsonObjectPartialMatcher(), + new StrictPrimitivePartialMatcher() + ); + + @Test + public void shouldIgnoreFooField() { + final var expected = "{\"foo\": \"bar\"}"; + final var received = "{\"foo\": \"foo\"}"; + + final var diff = DiffGenerator.diff(expected, received, jsonMatcher); + + assertEquals(100.0, diff.similarityRate()); + } + +} diff --git a/src/test/java/com/deblock/jsondiff/matcher/CompositeJsonMatcherTest.java b/src/test/java/com/deblock/jsondiff/matcher/CompositeJsonMatcherTest.java index bfadf03..0d36621 100644 --- a/src/test/java/com/deblock/jsondiff/matcher/CompositeJsonMatcherTest.java +++ b/src/test/java/com/deblock/jsondiff/matcher/CompositeJsonMatcherTest.java @@ -21,11 +21,11 @@ public void shouldCallTheArrayMatcherIfTheTwoObjectAreArray() { final var objectMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); final var primitiveMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); - Mockito.when(arrayMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(arrayMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isArray() && ((JsonNode)inv.getArgument(1)).isArray()); - Mockito.when(objectMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(objectMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isObject() && ((JsonNode)inv.getArgument(1)).isObject()); - Mockito.when(primitiveMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(primitiveMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isValueNode() && ((JsonNode)inv.getArgument(1)).isValueNode()); final var compositeMatcher = new CompositeJsonMatcher( @@ -50,11 +50,11 @@ public void shouldCallTheObjectMatcherIfTheTwoObjectAreObject() { final var objectMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); final var primitiveMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); - Mockito.when(arrayMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(arrayMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isArray() && ((JsonNode)inv.getArgument(1)).isArray()); - Mockito.when(objectMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(objectMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isObject() && ((JsonNode)inv.getArgument(1)).isObject()); - Mockito.when(primitiveMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(primitiveMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isValueNode() && ((JsonNode)inv.getArgument(1)).isValueNode()); final var compositeMatcher = new CompositeJsonMatcher( @@ -79,11 +79,11 @@ public void shouldCallThePrimitiveMatcherIfTheTwoObjectAreValue() { final var objectMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); final var primitiveMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); - Mockito.when(arrayMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(arrayMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isArray() && ((JsonNode)inv.getArgument(1)).isArray()); - Mockito.when(objectMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(objectMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isObject() && ((JsonNode)inv.getArgument(1)).isObject()); - Mockito.when(primitiveMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(primitiveMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isValueNode() && ((JsonNode)inv.getArgument(1)).isValueNode()); final var compositeMatcher = new CompositeJsonMatcher( @@ -108,11 +108,11 @@ public void shouldReturnANonMatchWhenTypesAreDifferent() { final var objectMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); final var primitiveMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); - Mockito.when(arrayMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(arrayMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isArray() && ((JsonNode)inv.getArgument(1)).isArray()); - Mockito.when(objectMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(objectMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isObject() && ((JsonNode)inv.getArgument(1)).isObject()); - Mockito.when(primitiveMatcher.manage(any(), any())).thenAnswer(inv -> + Mockito.when(primitiveMatcher.manage(null, any(), any())).thenAnswer(inv -> ((JsonNode)inv.getArgument(0)).isValueNode() && ((JsonNode)inv.getArgument(1)).isValueNode()); final var compositeMatcher = new CompositeJsonMatcher( diff --git a/src/test/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcherTest.java b/src/test/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcherTest.java index 1218fd4..00a607e 100644 --- a/src/test/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcherTest.java +++ b/src/test/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcherTest.java @@ -20,7 +20,7 @@ void manage_shouldReturnTrue_whenBothNodesAreNumbers() { final var number1 = IntNode.valueOf(10); final var number2 = DecimalNode.valueOf(BigDecimal.valueOf(20)); - assertTrue(matcher.manage(number1, number2)); + assertTrue(matcher.manage(null, number2, number1)); } @Test @@ -28,7 +28,7 @@ void manage_shouldReturnFalse_whenExpectedIsNotNumber() { final var string = StringNode.valueOf("test"); final var number = IntNode.valueOf(10); - assertFalse(matcher.manage(string, number)); + assertFalse(matcher.manage(null, number, string)); } @Test @@ -36,7 +36,7 @@ void manage_shouldReturnFalse_whenReceivedIsNotNumber() { final var number = IntNode.valueOf(10); final var string = StringNode.valueOf("test"); - assertFalse(matcher.manage(number, string)); + assertFalse(matcher.manage(null, string, number)); } @Test @@ -44,7 +44,7 @@ void manage_shouldReturnFalse_whenBothAreStrings() { final var string1 = StringNode.valueOf("test1"); final var string2 = StringNode.valueOf("test2"); - assertFalse(matcher.manage(string1, string2)); + assertFalse(matcher.manage(null, string2, string1)); } @Test @@ -52,7 +52,7 @@ void manage_shouldReturnFalse_whenBothAreBooleans() { final var bool1 = BooleanNode.TRUE; final var bool2 = BooleanNode.FALSE; - assertFalse(matcher.manage(bool1, bool2)); + assertFalse(matcher.manage(null, bool2, bool1)); } @Test diff --git a/src/test/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcherTest.java b/src/test/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcherTest.java index 56954e9..37c1f0d 100644 --- a/src/test/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcherTest.java +++ b/src/test/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcherTest.java @@ -4,9 +4,7 @@ import com.deblock.jsondiff.diff.UnMatchedPrimaryDiff; import org.junit.jupiter.api.Test; import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.NullNode; -import tools.jackson.databind.node.ObjectNode; import static org.junit.jupiter.api.Assertions.*; @@ -20,7 +18,7 @@ public void manage_shouldReturnTrue_whenExpectedIsNullAndReceivedIsArray() { final var nullNode = NullNode.getInstance(); final var arrayNode = MAPPER.createArrayNode(); - assertTrue(matcher.manage(nullNode, arrayNode)); + assertTrue(matcher.manage(null, arrayNode, nullNode)); } @Test @@ -28,7 +26,7 @@ public void manage_shouldReturnTrue_whenExpectedIsArrayAndReceivedIsNull() { final var arrayNode = MAPPER.createArrayNode(); final var nullNode = NullNode.getInstance(); - assertTrue(matcher.manage(arrayNode, nullNode)); + assertTrue(matcher.manage(null, nullNode, arrayNode)); } @Test @@ -36,7 +34,7 @@ public void manage_shouldReturnFalse_whenBothAreNull() { final var nullNode1 = NullNode.getInstance(); final var nullNode2 = NullNode.getInstance(); - assertFalse(matcher.manage(nullNode1, nullNode2)); + assertFalse(matcher.manage(null, nullNode2, nullNode1)); } @Test @@ -44,7 +42,7 @@ public void manage_shouldReturnFalse_whenBothAreArrays() { final var array1 = MAPPER.createArrayNode(); final var array2 = MAPPER.createArrayNode(); - assertFalse(matcher.manage(array1, array2)); + assertFalse(matcher.manage(null, array2, array1)); } @Test @@ -52,7 +50,7 @@ public void manage_shouldReturnFalse_whenExpectedIsNullAndReceivedIsObject() { final var nullNode = NullNode.getInstance(); final var objectNode = MAPPER.createObjectNode(); - assertFalse(matcher.manage(nullNode, objectNode)); + assertFalse(matcher.manage(null, objectNode, nullNode)); } @Test From 56961a381e879d4cd7380b4832b1ff70905f7f05 Mon Sep 17 00:00:00 2001 From: Thomas Deblock Date: Sun, 4 Jan 2026 20:46:43 +0100 Subject: [PATCH 2/3] refactor: patch matching management --- .../matcher/IgnoringFieldMatcher.java | 16 +- .../com/deblock/jsondiff/matcher/Path.java | 72 +++++-- .../deblock/jsondiff/matcher/PathMatcher.java | 128 +++++------ .../jsondiff/viewer/PatchDiffViewer.java | 90 ++++---- .../IgnoringFieldsIntegrationTest.java | 7 +- .../matcher/CompositeJsonMatcherTest.java | 48 ++--- .../matcher/IgnoringFieldMatcherTest.java | 172 +++++++++++++++ .../jsondiff/matcher/PathMatcherTest.java | 200 ++++++++++++++++++ 8 files changed, 560 insertions(+), 173 deletions(-) create mode 100644 src/test/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcherTest.java create mode 100644 src/test/java/com/deblock/jsondiff/matcher/PathMatcherTest.java diff --git a/src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java index ec7675a..a5003e1 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java @@ -6,17 +6,18 @@ import java.util.Arrays; import java.util.List; -import java.util.regex.Pattern; public class IgnoringFieldMatcher implements PartialJsonMatcher { - private final List fieldsToIgnore; + private final List fieldsToIgnore; - public IgnoringFieldMatcher(List patterns) { - this.fieldsToIgnore = patterns; + public IgnoringFieldMatcher(List paths) { + this.fieldsToIgnore = paths.stream() + .map(PathMatcher::from) + .toList(); } - public IgnoringFieldMatcher(Pattern... patterns) { - this.fieldsToIgnore = Arrays.stream(patterns).toList(); + public IgnoringFieldMatcher(String ...paths) { + this(Arrays.stream(paths).toList()); } @Override @@ -26,7 +27,6 @@ public JsonDiff jsonDiff(Path path, JsonNode expectedJson, JsonNode receivedJson @Override public boolean manage(Path path, JsonNode expected, JsonNode received) { - String stringPath = path.toString(); - return fieldsToIgnore.stream().anyMatch(pattern -> pattern.matcher(stringPath).matches()); + return fieldsToIgnore.stream().anyMatch(pattern -> pattern.match(path)); } } diff --git a/src/main/java/com/deblock/jsondiff/matcher/Path.java b/src/main/java/com/deblock/jsondiff/matcher/Path.java index e90add4..a8f1fe7 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/Path.java +++ b/src/main/java/com/deblock/jsondiff/matcher/Path.java @@ -2,37 +2,75 @@ import java.util.Objects; +/** + * Represents a JSON path (e.g., $.property.0.subproperty). + * Stored in reverse order (last element at head) for O(1) add operations + * and efficient end-matching in PathMatcher. + */ public class Path { public static final Path ROOT = new Path(); - public final PathItem property; - public final Path next; + private final PathItem last; + private final Path previous; public Path() { this(null, null); } - private Path(PathItem property, Path next) { - this.property = property; - this.next = next; + private Path(PathItem last, Path previous) { + this.last = last; + this.previous = previous; } - private Path(PathItem property) { - this.property = property; - this.next = null; + public Path add(PathItem item) { + if (this.last == null) { + return new Path(item, null); + } + return new Path(item, this); } - public Path add(PathItem item) { - if (this.next == null) { - return new Path(this.property, new Path(item)); - } else { - return new Path(this.property, this.next.add(item)); + public PathItem item() { + return last; + } + + /** + * Returns the path without its last element. + */ + public Path previous() { + return previous == null ? ROOT : previous; + } + + /** + * Returns the path items in natural order (from root to leaf). + * This is useful for traversing the path from start to end. + */ + public java.util.List toList() { + java.util.List result = new java.util.ArrayList<>(); + collectItems(result); + return result; + } + + private void collectItems(java.util.List result) { + if (last == null) return; + if (previous != null) { + previous.collectItems(result); } + result.add(last); } + @Override public String toString() { - return ((this.property == null) ? "$" : this.property) + - ((this.next == null) ? "" : "." + this.next); + StringBuilder sb = new StringBuilder("$"); + appendReversed(sb); + return sb.toString(); + } + + private void appendReversed(StringBuilder sb) { + if (last == null) return; + if (previous != null) { + previous.appendReversed(sb); + } + sb.append(".").append(last); } @Override @@ -40,12 +78,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Path path = (Path) o; - return Objects.equals(property, path.property) && Objects.equals(next, path.next); + return Objects.equals(last, path.last) && Objects.equals(previous, path.previous); } @Override public int hashCode() { - return Objects.hash(property, next); + return Objects.hash(last, previous); } public interface PathItem { diff --git a/src/main/java/com/deblock/jsondiff/matcher/PathMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/PathMatcher.java index 151272a..5b61d33 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/PathMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/PathMatcher.java @@ -1,128 +1,102 @@ package com.deblock.jsondiff.matcher; - public class PathMatcher { - public final PathMatcher.PathMatcherItem property; - public final PathMatcher next; + public final PathMatcherItem last; + public final PathMatcher previous; public static PathMatcher from(String path) { PathMatcher matcher = new PathMatcher(); - for (String part: path.split("\\.")) { + for (String part : path.split("\\.")) { + if (part.isEmpty()) { + throw new IllegalArgumentException("path matcher part can not be empty"); + } if (part.endsWith("]")) { String index = part.substring(part.lastIndexOf("[") + 1, part.length() - 1); - matcher = matcher.add(new PathMatcherItem.ObjectProperty(part.substring(0, part.lastIndexOf("[")))); - if ("*".equals(index)) { - matcher = matcher.add(new PathMatcherItem.WilcardMatcherItem(Path.PathItem.ArrayIndex.class)); - } else { - matcher = matcher.add(new PathMatcherItem.ArrayIndex(Integer.parseInt(index))); - } - } else if ("*".equals(part)) { - matcher = matcher.add(new PathMatcherItem.WilcardMatcherItem(Path.PathItem.ObjectProperty.class)); + matcher = matcher.add(PathMatcherItem.ofProperty(part.substring(0, part.lastIndexOf("[")))); + matcher = matcher.add(PathMatcherItem.ofArrayIndex(index)); } else { - matcher = matcher.add(new PathMatcher.PathMatcherItem.ObjectProperty(part)); + matcher = matcher.add(PathMatcherItem.ofProperty(part)); } } return matcher; } - public PathMatcher() { + private PathMatcher() { this(null, null); } - private PathMatcher(PathMatcher.PathMatcherItem property, PathMatcher next) { - this.property = property; - this.next = next; + private PathMatcher(PathMatcherItem last, PathMatcher previous) { + this.last = last; + this.previous = previous; } - private PathMatcher(PathMatcher.PathMatcherItem property) { - this.property = property; - this.next = null; - } - - public PathMatcher add(PathMatcher.PathMatcherItem item) { - if (this.next == null) { - return new PathMatcher(this.property, new PathMatcher(item)); - } else { - return new PathMatcher(this.property, this.next.add(item)); + private PathMatcher add(PathMatcherItem item) { + if (this.last == null) { + return new PathMatcher(item, null); } + return new PathMatcher(item, this); } public boolean match(Path path) { - int pathLength = length(path); - int matcherLength = length(); + if (this.last == null) { + return true; + } - if (matcherLength > pathLength) { + if (path == null || path.item() == null) { return false; } - // Align path to match from the end - Path alignedPath = path; - for (int i = 0; i < pathLength - matcherLength; i++) { - alignedPath = alignedPath.next; + if (!this.last.match(path.item())) { + return false; } - return matchFromHere(alignedPath); - } - - private int length() { - int count = 0; - PathMatcher current = this; - while (current != null && current.property != null) { - count++; - current = current.next; + if (this.previous == null) { + return true; } - return count; - } - private int length(Path path) { - int count = 0; - Path current = path; - while (current != null && current.property != null) { - count++; - current = current.next; - } - return count; + return this.previous.match(path.previous()); } - private boolean matchFromHere(Path path) { - if (this.property == null) { - return next == null || next.matchFromHere(path); - } - if (path == null || path.property == null) { - return false; - } - if (this.property.match(path.property)) { - if (next == null) { - return path.next == null || path.next.property == null; - } - return path.next != null && next.matchFromHere(path.next); - } - return false; + @Override + public String toString() { + StringBuilder sb = new StringBuilder("$"); + appendReversed(sb); + return sb.toString(); } - public String toString() { - return ((this.property == null) ? "$" : this.property) + - ((this.next == null) ? "" : "." + this.next); + private void appendReversed(StringBuilder sb) { + if (last == null) return; + if (previous != null) { + previous.appendReversed(sb); + } + sb.append(".").append(last); } - private interface PathMatcherItem { - static PathMatcherItem of(String property) { - return new PathMatcher.PathMatcherItem.ObjectProperty(property); + public interface PathMatcherItem { + static PathMatcherItem ofProperty(String property) { + if ("*".equals(property)) { + return new WildcardMatcherItem(Path.PathItem.ObjectProperty.class); + } + return new ObjectProperty(property); } - static PathMatcherItem of(Integer index) { - return new PathMatcher.PathMatcherItem.ArrayIndex(index); + static PathMatcherItem ofArrayIndex(String index) { + if ("*".equals(index)) { + return new WildcardMatcherItem(Path.PathItem.ArrayIndex.class); + } + return new ArrayIndex(Integer.parseInt(index)); } boolean match(Path.PathItem pathItem); - class WilcardMatcherItem implements PathMatcherItem { + class WildcardMatcherItem implements PathMatcherItem { private final Class type; - public WilcardMatcherItem(Class type) { + public WildcardMatcherItem(Class type) { this.type = type; } + @Override public String toString() { return "*"; } diff --git a/src/main/java/com/deblock/jsondiff/viewer/PatchDiffViewer.java b/src/main/java/com/deblock/jsondiff/viewer/PatchDiffViewer.java index 7d62b8a..46fc67e 100644 --- a/src/main/java/com/deblock/jsondiff/viewer/PatchDiffViewer.java +++ b/src/main/java/com/deblock/jsondiff/viewer/PatchDiffViewer.java @@ -53,44 +53,54 @@ public void primaryNonMatching(Path path, JsonNode expected, JsonNode value) { public Object addPath(Object root, Path path, DiffValue diffValue) { if (path == null) { return diffValue; - } else if (path.property instanceof Path.PathItem.ArrayIndex) { - final var index = ((Path.PathItem.ArrayIndex) path.property).index; + } + + var items = path.toList(); + if (items.isEmpty()) { + return diffValue; + } + + return addPathItems(root, items, 0, diffValue); + } + + private Object addPathItems(Object root, java.util.List items, int index, DiffValue diffValue) { + if (index >= items.size()) { + return diffValue; + } + + var item = items.get(index); + boolean isLast = index == items.size() - 1; + + if (item instanceof Path.PathItem.ArrayIndex arrayItem) { + final var arrayIndex = arrayItem.index; if (root == null) { final var newRoot = new DiffValue.ArrayDiff(); - newRoot.set(index, this.addPath(null, path.next, diffValue)); + newRoot.set(arrayIndex, isLast ? diffValue : this.addPathItems(null, items, index + 1, diffValue)); return newRoot; - } else if (root instanceof DiffValue.ArrayDiff) { - final var array = (DiffValue.ArrayDiff) root; - if (array.hasIndex(index) && !(diffValue instanceof DiffValue.ExtraProperty)) { - this.addPath(array.get(index), path.next, diffValue); + } else if (root instanceof DiffValue.ArrayDiff arrayDiff) { + if (arrayDiff.hasIndex(arrayIndex) && !(diffValue instanceof DiffValue.ExtraProperty)) { + this.addPathItems(arrayDiff.get(arrayIndex), items, index + 1, diffValue); } else { - array.set(index, this.addPath(array.get(index), path.next, diffValue)); + arrayDiff.set(arrayIndex, isLast ? diffValue : this.addPathItems(arrayDiff.get(arrayIndex), items, index + 1, diffValue)); } - return array; + return arrayDiff; } else { - throw new IllegalArgumentException("The path " + path + " is not an array"); + throw new IllegalArgumentException("The path is not an array at index " + arrayIndex); } - } else if (path.property instanceof Path.PathItem.ObjectProperty) { - final var propertyName = ((Path.PathItem.ObjectProperty) path.property).property; + } else if (item instanceof Path.PathItem.ObjectProperty objectItem) { + final var propertyName = objectItem.property; if (root == null) { final var newRoot = new HashMap(); - newRoot.put(propertyName, this.addPath(null, path.next, diffValue)); + newRoot.put(propertyName, isLast ? diffValue : this.addPathItems(null, items, index + 1, diffValue)); return newRoot; - } else if (root instanceof Map) { - final var map = (Map) root; - map.put(propertyName, this.addPath(map.get(propertyName), path.next, diffValue)); + } else if (root instanceof Map map) { + map.put(propertyName, isLast ? diffValue : this.addPathItems(map.get(propertyName), items, index + 1, diffValue)); return map; } else { - throw new IllegalArgumentException("The path " + path + " is not an object"); - } - } else if (path.property == null) { - if (path.next != null) { - return this.addPath(root, path.next, diffValue); - } else { - return diff; + throw new IllegalArgumentException("The path is not an object at property " + propertyName); } } else { - throw new IllegalArgumentException("Unsupported path type " + path.property.getClass()); + throw new IllegalArgumentException("Unsupported path item type " + item.getClass()); } } @@ -99,9 +109,9 @@ public String toString() { } private String toDiff(Object diff, String indent, String startOfLine, String endOfLineExpected, String endOfLineActual) { - if (diff instanceof DiffValue.ArrayDiff) { + if (diff instanceof DiffValue.ArrayDiff arrayDiff) { final var arrayContent = new StringBuilder(); - final var allObjects = ((DiffValue.ArrayDiff) diff).allObjects(); + final var allObjects = arrayDiff.allObjects(); for (int i = 0; i < allObjects.size(); ++i) { final var object = allObjects.get(i); arrayContent @@ -115,10 +125,9 @@ private String toDiff(Object diff, String indent, String startOfLine, String end .append("\n"); } return startOfLine + " [\n" + arrayContent + indent + " ]" + endOfLineExpected; - } else if (diff instanceof Map) { + } else if (diff instanceof Map diffObject) { final var objectContent = new StringBuilder(); - final var diffObject = (Map) diff; - final var keys = new ArrayList<>(diffObject.keySet()); + final var keys = new ArrayList(diffObject.keySet()); for (int i = 0; i < keys.size(); ++i) { final var object = diffObject.get(keys.get(i)); final var isObjectNotADiff = object instanceof DiffValue.ArrayDiff || object instanceof Map; @@ -135,19 +144,18 @@ private String toDiff(Object diff, String indent, String startOfLine, String end .append("\n"); } return startOfLine + " {\n" + objectContent + indent + " }" + endOfLineExpected; - } else if (diff instanceof DiffValue.MatchingProperty) { + } else if (diff instanceof DiffValue.MatchingProperty matchingDiff) { if (endOfLineActual.equals(endOfLineExpected)) { - return " " + indent + ((DiffValue.MatchingProperty) diff).value.toString() + endOfLineActual; + return " " + indent + matchingDiff.value.toString() + endOfLineActual; } else { - return "-" + indent + ((DiffValue.MatchingProperty) diff).value.toString() + endOfLineActual + "\n" + - "+" + indent + ((DiffValue.MatchingProperty) diff).value.toString() + endOfLineExpected; + return "-" + indent + matchingDiff.value.toString() + endOfLineActual + "\n" + + "+" + indent + matchingDiff.value.toString() + endOfLineExpected; } - } else if (diff instanceof DiffValue.MissingProperty) { - return "+" + indent + ((DiffValue.MissingProperty) diff).value.toString() + endOfLineExpected; - } else if (diff instanceof DiffValue.ExtraProperty) { - return "-" + indent + ((DiffValue.ExtraProperty) diff).value.toString() + endOfLineActual; - } else if (diff instanceof DiffValue.NonMatchingProperty) { - final var value = ((DiffValue.NonMatchingProperty) diff); + } else if (diff instanceof DiffValue.MissingProperty missingPropertyDiff) { + return "+" + indent + missingPropertyDiff.value.toString() + endOfLineExpected; + } else if (diff instanceof DiffValue.ExtraProperty extraPropertyDiff) { + return "-" + indent + extraPropertyDiff.value.toString() + endOfLineActual; + } else if (diff instanceof DiffValue.NonMatchingProperty value) { return "-" + indent + value.value.toString() + endOfLineActual + "\n+" + indent + value.expected.toString() + endOfLineExpected; } else { throw new IllegalArgumentException("Unsupported diff type " + diff.getClass()); @@ -214,8 +222,8 @@ public static class ArrayDiff extends DiffValue { public final List extraProperty = new ArrayList<>(); public void set(int index, Object object) { - if (object instanceof DiffValue.ExtraProperty) { - extraProperty.add((DiffValue.ExtraProperty) object); + if (object instanceof DiffValue.ExtraProperty extraPropertyDiff) { + extraProperty.add(extraPropertyDiff); } else { while (diffs.size() <= index) { diffs.add(null); diff --git a/src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java b/src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java index c7674f6..c967a5b 100644 --- a/src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java +++ b/src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java @@ -4,17 +4,12 @@ import com.deblock.jsondiff.matcher.*; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.regex.Pattern; - import static org.junit.jupiter.api.Assertions.assertEquals; public class IgnoringFieldsIntegrationTest { private final CompositeJsonMatcher jsonMatcher = new CompositeJsonMatcher( - new IgnoringFieldMatcher(List.of( - Pattern.compile("\\$\\..*foo") - )), + new IgnoringFieldMatcher("foo"), new LenientJsonArrayPartialMatcher(), new LenientJsonObjectPartialMatcher(), new StrictPrimitivePartialMatcher() diff --git a/src/test/java/com/deblock/jsondiff/matcher/CompositeJsonMatcherTest.java b/src/test/java/com/deblock/jsondiff/matcher/CompositeJsonMatcherTest.java index 0d36621..88d79c1 100644 --- a/src/test/java/com/deblock/jsondiff/matcher/CompositeJsonMatcherTest.java +++ b/src/test/java/com/deblock/jsondiff/matcher/CompositeJsonMatcherTest.java @@ -21,12 +21,12 @@ public void shouldCallTheArrayMatcherIfTheTwoObjectAreArray() { final var objectMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); final var primitiveMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); - Mockito.when(arrayMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isArray() && ((JsonNode)inv.getArgument(1)).isArray()); - Mockito.when(objectMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isObject() && ((JsonNode)inv.getArgument(1)).isObject()); - Mockito.when(primitiveMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isValueNode() && ((JsonNode)inv.getArgument(1)).isValueNode()); + Mockito.when(arrayMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isArray() && ((JsonNode)inv.getArgument(2)).isArray()); + Mockito.when(objectMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isObject() && ((JsonNode)inv.getArgument(2)).isObject()); + Mockito.when(primitiveMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isValueNode() && ((JsonNode)inv.getArgument(2)).isValueNode()); final var compositeMatcher = new CompositeJsonMatcher( arrayMatcher, @@ -50,12 +50,12 @@ public void shouldCallTheObjectMatcherIfTheTwoObjectAreObject() { final var objectMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); final var primitiveMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); - Mockito.when(arrayMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isArray() && ((JsonNode)inv.getArgument(1)).isArray()); - Mockito.when(objectMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isObject() && ((JsonNode)inv.getArgument(1)).isObject()); - Mockito.when(primitiveMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isValueNode() && ((JsonNode)inv.getArgument(1)).isValueNode()); + Mockito.when(arrayMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isArray() && ((JsonNode)inv.getArgument(2)).isArray()); + Mockito.when(objectMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isObject() && ((JsonNode)inv.getArgument(2)).isObject()); + Mockito.when(primitiveMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isValueNode() && ((JsonNode)inv.getArgument(2)).isValueNode()); final var compositeMatcher = new CompositeJsonMatcher( arrayMatcher, @@ -79,12 +79,12 @@ public void shouldCallThePrimitiveMatcherIfTheTwoObjectAreValue() { final var objectMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); final var primitiveMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); - Mockito.when(arrayMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isArray() && ((JsonNode)inv.getArgument(1)).isArray()); - Mockito.when(objectMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isObject() && ((JsonNode)inv.getArgument(1)).isObject()); - Mockito.when(primitiveMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isValueNode() && ((JsonNode)inv.getArgument(1)).isValueNode()); + Mockito.when(arrayMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isArray() && ((JsonNode)inv.getArgument(2)).isArray()); + Mockito.when(objectMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isObject() && ((JsonNode)inv.getArgument(2)).isObject()); + Mockito.when(primitiveMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isValueNode() && ((JsonNode)inv.getArgument(2)).isValueNode()); final var compositeMatcher = new CompositeJsonMatcher( arrayMatcher, @@ -108,12 +108,12 @@ public void shouldReturnANonMatchWhenTypesAreDifferent() { final var objectMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); final var primitiveMatcher = (PartialJsonMatcher) Mockito.mock(PartialJsonMatcher.class); - Mockito.when(arrayMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isArray() && ((JsonNode)inv.getArgument(1)).isArray()); - Mockito.when(objectMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isObject() && ((JsonNode)inv.getArgument(1)).isObject()); - Mockito.when(primitiveMatcher.manage(null, any(), any())).thenAnswer(inv -> - ((JsonNode)inv.getArgument(0)).isValueNode() && ((JsonNode)inv.getArgument(1)).isValueNode()); + Mockito.when(arrayMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isArray() && ((JsonNode)inv.getArgument(2)).isArray()); + Mockito.when(objectMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isObject() && ((JsonNode)inv.getArgument(2)).isObject()); + Mockito.when(primitiveMatcher.manage(any(), any(), any())).thenAnswer(inv -> + ((JsonNode)inv.getArgument(1)).isValueNode() && ((JsonNode)inv.getArgument(2)).isValueNode()); final var compositeMatcher = new CompositeJsonMatcher( arrayMatcher, diff --git a/src/test/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcherTest.java b/src/test/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcherTest.java new file mode 100644 index 0000000..632420f --- /dev/null +++ b/src/test/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcherTest.java @@ -0,0 +1,172 @@ +package com.deblock.jsondiff.matcher; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import tools.jackson.databind.node.IntNode; +import tools.jackson.databind.node.StringNode; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class IgnoringFieldMatcherTest { + + @Nested + class ManageMethod { + + @Test + void shouldMatchExactPath() { + var matcher = new IgnoringFieldMatcher("name"); + var path = Path.ROOT.add(Path.PathItem.of("name")); + + assertTrue(matcher.manage(path, null, null)); + } + + @Test + void shouldMatchEndOfPath() { + var matcher = new IgnoringFieldMatcher("name"); + var path = Path.ROOT + .add(Path.PathItem.of("user")) + .add(Path.PathItem.of("name")); + + assertTrue(matcher.manage(path, null, null)); + } + + @Test + void shouldNotMatchDifferentPath() { + var matcher = new IgnoringFieldMatcher("name"); + var path = Path.ROOT.add(Path.PathItem.of("age")); + + assertFalse(matcher.manage(path, null, null)); + } + + @Test + void shouldMatchNestedPath() { + var matcher = new IgnoringFieldMatcher("user.name"); + var path = Path.ROOT + .add(Path.PathItem.of("data")) + .add(Path.PathItem.of("user")) + .add(Path.PathItem.of("name")); + + assertTrue(matcher.manage(path, null, null)); + } + + @Test + void shouldNotMatchPartialNestedPath() { + var matcher = new IgnoringFieldMatcher("user.name"); + var path = Path.ROOT + .add(Path.PathItem.of("name")); + + assertFalse(matcher.manage(path, null, null)); + } + + @Test + void shouldMatchWithWildcardProperty() { + var matcher = new IgnoringFieldMatcher("*.name"); + var path = Path.ROOT + .add(Path.PathItem.of("user")) + .add(Path.PathItem.of("name")); + + assertTrue(matcher.manage(path, null, null)); + } + + @Test + void shouldMatchArrayIndex() { + var matcher = new IgnoringFieldMatcher("items[0]"); + var path = Path.ROOT + .add(Path.PathItem.of("items")) + .add(Path.PathItem.of(0)); + + assertTrue(matcher.manage(path, null, null)); + } + + @Test + void shouldMatchArrayWildcard() { + var matcher = new IgnoringFieldMatcher("items[*].id"); + var path = Path.ROOT + .add(Path.PathItem.of("items")) + .add(Path.PathItem.of(5)) + .add(Path.PathItem.of("id")); + + assertTrue(matcher.manage(path, null, null)); + } + + @Test + void shouldMatchAnyOfMultiplePatterns() { + var matcher = new IgnoringFieldMatcher("name", "age", "email"); + var pathName = Path.ROOT.add(Path.PathItem.of("name")); + var pathAge = Path.ROOT.add(Path.PathItem.of("age")); + var pathEmail = Path.ROOT.add(Path.PathItem.of("email")); + var pathCity = Path.ROOT.add(Path.PathItem.of("city")); + + assertTrue(matcher.manage(pathName, null, null)); + assertTrue(matcher.manage(pathAge, null, null)); + assertTrue(matcher.manage(pathEmail, null, null)); + assertFalse(matcher.manage(pathCity, null, null)); + } + + @Test + void shouldWorkWithListConstructor() { + var matcher = new IgnoringFieldMatcher(List.of("name", "age")); + var pathName = Path.ROOT.add(Path.PathItem.of("name")); + var pathAge = Path.ROOT.add(Path.PathItem.of("age")); + + assertTrue(matcher.manage(pathName, null, null)); + assertTrue(matcher.manage(pathAge, null, null)); + } + + @Test + void shouldNotMatchRootPath() { + var matcher = new IgnoringFieldMatcher("name"); + + assertFalse(matcher.manage(Path.ROOT, null, null)); + } + } + + @Nested + class JsonDiffMethod { + + @Test + void shouldReturnMatchedDiffWithFullSimilarity() { + var matcher = new IgnoringFieldMatcher("name"); + var path = Path.ROOT.add(Path.PathItem.of("name")); + var expected = StringNode.valueOf("John"); + var received = StringNode.valueOf("Jane"); + + var diff = matcher.jsonDiff(path, expected, received, Mockito.mock(JsonMatcher.class)); + + assertEquals(100, diff.similarityRate()); + assertEquals(path, diff.path()); + } + + @Test + void shouldReturnMatchedDiffEvenWithDifferentTypes() { + var matcher = new IgnoringFieldMatcher("value"); + var path = Path.ROOT.add(Path.PathItem.of("value")); + var expected = StringNode.valueOf("100"); + var received = IntNode.valueOf(100); + + var diff = matcher.jsonDiff(path, expected, received, Mockito.mock(JsonMatcher.class)); + + assertEquals(100, diff.similarityRate()); + } + + @Test + void shouldReturnMatchedDiffForNestedPath() { + var matcher = new IgnoringFieldMatcher("user.timestamp"); + var path = Path.ROOT + .add(Path.PathItem.of("user")) + .add(Path.PathItem.of("timestamp")); + var expected = StringNode.valueOf("2024-01-01"); + var received = StringNode.valueOf("2024-12-31"); + + var diff = matcher.jsonDiff(path, expected, received, Mockito.mock(JsonMatcher.class)); + + assertEquals(100, diff.similarityRate()); + new JsonDiffAsserter() + .assertPrimaryMatching(path) + .validate(diff); + } + } +} diff --git a/src/test/java/com/deblock/jsondiff/matcher/PathMatcherTest.java b/src/test/java/com/deblock/jsondiff/matcher/PathMatcherTest.java new file mode 100644 index 0000000..0851d4d --- /dev/null +++ b/src/test/java/com/deblock/jsondiff/matcher/PathMatcherTest.java @@ -0,0 +1,200 @@ +package com.deblock.jsondiff.matcher; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class PathMatcherTest { + + @Nested + class ExactMatching { + + @Test + void shouldMatchExactProperty() { + var matcher = PathMatcher.from("foo"); + var path = Path.ROOT.add(Path.PathItem.of("foo")); + + assertTrue(matcher.match(path)); + } + + @Test + void shouldMatchExactNestedPath() { + var matcher = PathMatcher.from("foo.bar"); + var path = Path.ROOT + .add(Path.PathItem.of("foo")) + .add(Path.PathItem.of("bar")); + + assertTrue(matcher.match(path)); + } + + @Test + void shouldNotMatchDifferentProperty() { + var matcher = PathMatcher.from("foo"); + var path = Path.ROOT.add(Path.PathItem.of("bar")); + + assertFalse(matcher.match(path)); + } + + @Test + void shouldMatchArrayIndex() { + var matcher = PathMatcher.from("items[0]"); + var path = Path.ROOT + .add(Path.PathItem.of("items")) + .add(Path.PathItem.of(0)); + + assertTrue(matcher.match(path)); + } + + @Test + void shouldNotMatchDifferentArrayIndex() { + var matcher = PathMatcher.from("items[0]"); + var path = Path.ROOT + .add(Path.PathItem.of("items")) + .add(Path.PathItem.of(1)); + + assertFalse(matcher.match(path)); + } + } + + @Nested + class WildcardMatching { + + @Test + void shouldMatchAnyPropertyWithWildcard() { + var matcher = PathMatcher.from("foo.*.baz"); + var path = Path.ROOT + .add(Path.PathItem.of("foo")) + .add(Path.PathItem.of("bar")) + .add(Path.PathItem.of("baz")); + + assertTrue(matcher.match(path)); + } + + @Test + void shouldMatchAnyArrayIndexWithWildcard() { + var matcher = PathMatcher.from("items[*]"); + var path0 = Path.ROOT.add(Path.PathItem.of("items")).add(Path.PathItem.of(0)); + var path5 = Path.ROOT.add(Path.PathItem.of("items")).add(Path.PathItem.of(5)); + var path99 = Path.ROOT.add(Path.PathItem.of("items")).add(Path.PathItem.of(99)); + + assertTrue(matcher.match(path0)); + assertTrue(matcher.match(path5)); + assertTrue(matcher.match(path99)); + } + + @Test + void shouldNotMatchPropertyWildcardWithArrayIndex() { + var matcher = PathMatcher.from("foo.*"); + var path = Path.ROOT + .add(Path.PathItem.of("foo")) + .add(Path.PathItem.of(0)); + + assertFalse(matcher.match(path)); + } + + @Test + void shouldNotMatchArrayWildcardWithProperty() { + var matcher = PathMatcher.from("items[*]"); + var path = Path.ROOT + .add(Path.PathItem.of("items")) + .add(Path.PathItem.of("notAnIndex")); + + assertFalse(matcher.match(path)); + } + } + + @Nested + class EndsWithMatching { + + @Test + void shouldMatchAtEndOfPath() { + var matcher = PathMatcher.from("bar"); + var path = Path.ROOT + .add(Path.PathItem.of("foo")) + .add(Path.PathItem.of("bar")); + + assertTrue(matcher.match(path)); + } + + @Test + void shouldMatchNestedPatternAtEndOfPath() { + var matcher = PathMatcher.from("foo.bar"); + var path = Path.ROOT + .add(Path.PathItem.of("prefix")) + .add(Path.PathItem.of("foo")) + .add(Path.PathItem.of("bar")); + + assertTrue(matcher.match(path)); + } + + @Test + void shouldNotMatchPropertyInMiddleOfPath() { + var matcher = PathMatcher.from("foo"); + var path = Path.ROOT + .add(Path.PathItem.of("test")) + .add(Path.PathItem.of("foo")) + .add(Path.PathItem.of("bar")); + + assertFalse(matcher.match(path)); + } + + @Test + void shouldNotMatchNestedPatternInMiddleOfPath() { + var matcher = PathMatcher.from("foo.bar"); + var path = Path.ROOT + .add(Path.PathItem.of("prefix")) + .add(Path.PathItem.of("foo")) + .add(Path.PathItem.of("bar")) + .add(Path.PathItem.of("suffix")); + + assertFalse(matcher.match(path)); + } + + @Test + void shouldNotMatchIfPatternNotInPath() { + var matcher = PathMatcher.from("notfound"); + var path = Path.ROOT + .add(Path.PathItem.of("foo")) + .add(Path.PathItem.of("bar")); + + assertFalse(matcher.match(path)); + } + + @Test + void shouldMatchArrayAtEndOfPath() { + var matcher = PathMatcher.from("items[*].name"); + var path = Path.ROOT + .add(Path.PathItem.of("data")) + .add(Path.PathItem.of("items")) + .add(Path.PathItem.of(0)) + .add(Path.PathItem.of("name")); + + assertTrue(matcher.match(path)); + } + + @Test + void shouldNotMatchWhenPathIsShorterThanMatcher() { + var matcher = PathMatcher.from("foo.bar.baz"); + var path = Path.ROOT + .add(Path.PathItem.of("bar")) + .add(Path.PathItem.of("baz")); + + assertFalse(matcher.match(path)); + } + } + + @Nested + class EdgeCases { + + @Test + void shouldThrowExceptionForEmptyPathMatching() { + assertThrows(IllegalArgumentException.class, () -> PathMatcher.from("")); + } + + @Test + void shouldThrowExceptionForEmptyPathMatchingBetweenDot() { + assertThrows(IllegalArgumentException.class, () -> PathMatcher.from("foo..bar")); + } + } +} From 1c6565b8ba405f7fb0002afaa2b68b0cb2776a25 Mon Sep 17 00:00:00 2001 From: Thomas Deblock Date: Tue, 6 Jan 2026 19:04:21 +0100 Subject: [PATCH 3/3] refactor: rename class and add readme section --- README.md | 59 +++++++++++++++++++ ...ldMatcher.java => IgnoredPathMatcher.java} | 12 ++-- ...t.java => IgnoredPathIntegrationTest.java} | 4 +- ...rTest.java => IgnoredPathMatcherTest.java} | 30 +++++----- 4 files changed, 82 insertions(+), 23 deletions(-) rename src/main/java/com/deblock/jsondiff/matcher/{IgnoringFieldMatcher.java => IgnoredPathMatcher.java} (64%) rename src/test/java/com/deblock/jsondiff/integration/{IgnoringFieldsIntegrationTest.java => IgnoredPathIntegrationTest.java} (89%) rename src/test/java/com/deblock/jsondiff/matcher/{IgnoringFieldMatcherTest.java => IgnoredPathMatcherTest.java} (84%) diff --git a/README.md b/README.md index e0c718b..0f93dfd 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ final var mixedMatcher = new CompositeJsonMatcher( | Matcher | Description | |---------|-------------| | `NullEqualsEmptyArrayMatcher` | Treats `null` and `[]` as equivalent | +| `IgnoredPathMatcher` | Ignores specified fields during comparison | ## Treating Null as Empty Array @@ -196,6 +197,64 @@ System.out.println(diff.similarityRate()); // 100.0 - This matcher only handles `null` vs empty array `[]`, not missing properties - Non-empty arrays do not match `null` +## Ignoring path + +The `IgnoredPathMatcher` allows you to ignore specific fields during comparison. This is useful for fields like timestamps, IDs, or other dynamic values that you don't want to compare. + +```java +final var jsonMatcher = new CompositeJsonMatcher( + new IgnoredPathMatcher("timestamp", "id"), // Must be first + new LenientJsonArrayPartialMatcher(), + new LenientJsonObjectPartialMatcher(), + new StrictPrimitivePartialMatcher() +); + +// These will match with 100% similarity: +final var diff = DiffGenerator.diff( + "{\"name\": \"John\", \"timestamp\": \"2024-01-01\"}", + "{\"name\": \"John\", \"timestamp\": \"2024-12-31\"}", + jsonMatcher +); + +System.out.println(diff.similarityRate()); // 100.0 +``` + +### Path Patterns + +The `IgnoredPathMatcher` supports various path patterns: + +| Pattern | Description | Example | +|---------|-------------|---------| +| `name` | Matches field `name` at any level | Ignores `$.name`, `$.user.name`, `$.data.user.name` | +| `user.name` | Matches `name` under `user` | Ignores `$.user.name`, `$.data.user.name` | +| `*.name` | Wildcard for any property | Ignores `$.foo.name`, `$.bar.name` | +| `items[0]` | Matches specific array index | Ignores `$.items[0]` | +| `items[*]` | Wildcard for any array index | Ignores `$.items[0]`, `$.items[1]`, etc. | +| `items[*].id` | Field in any array element | Ignores `$.items[0].id`, `$.items[5].id` | + +### Examples + +```java +// Ignore a single field everywhere +new IgnoredPathMatcher("createdAt") + +// Ignore multiple fields +new IgnoredPathMatcher("createdAt", "updatedAt", "id") + +// Ignore nested field +new IgnoredPathMatcher("metadata.timestamp") + +// Ignore field in all array elements +new IgnoredPathMatcher("users[*].password") + +// Combine multiple patterns +new IgnoredPathMatcher("id", "*.createdAt", "items[*].internalId") +``` + +**Important:** +- Place `IgnoredPathMatcher` **before** other matchers in the constructor +- Patterns match against the end of the path, so `name` matches `$.user.name` as well as `$.name` + ## Advanced Example ```java diff --git a/src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/IgnoredPathMatcher.java similarity index 64% rename from src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java rename to src/main/java/com/deblock/jsondiff/matcher/IgnoredPathMatcher.java index a5003e1..48e72ef 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/IgnoredPathMatcher.java @@ -7,16 +7,16 @@ import java.util.Arrays; import java.util.List; -public class IgnoringFieldMatcher implements PartialJsonMatcher { - private final List fieldsToIgnore; +public class IgnoredPathMatcher implements PartialJsonMatcher { + private final List pathsToIgnore; - public IgnoringFieldMatcher(List paths) { - this.fieldsToIgnore = paths.stream() + public IgnoredPathMatcher(List paths) { + this.pathsToIgnore = paths.stream() .map(PathMatcher::from) .toList(); } - public IgnoringFieldMatcher(String ...paths) { + public IgnoredPathMatcher(String ...paths) { this(Arrays.stream(paths).toList()); } @@ -27,6 +27,6 @@ public JsonDiff jsonDiff(Path path, JsonNode expectedJson, JsonNode receivedJson @Override public boolean manage(Path path, JsonNode expected, JsonNode received) { - return fieldsToIgnore.stream().anyMatch(pattern -> pattern.match(path)); + return pathsToIgnore.stream().anyMatch(pattern -> pattern.match(path)); } } diff --git a/src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java b/src/test/java/com/deblock/jsondiff/integration/IgnoredPathIntegrationTest.java similarity index 89% rename from src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java rename to src/test/java/com/deblock/jsondiff/integration/IgnoredPathIntegrationTest.java index c967a5b..75375d3 100644 --- a/src/test/java/com/deblock/jsondiff/integration/IgnoringFieldsIntegrationTest.java +++ b/src/test/java/com/deblock/jsondiff/integration/IgnoredPathIntegrationTest.java @@ -6,10 +6,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class IgnoringFieldsIntegrationTest { +public class IgnoredPathIntegrationTest { private final CompositeJsonMatcher jsonMatcher = new CompositeJsonMatcher( - new IgnoringFieldMatcher("foo"), + new IgnoredPathMatcher("foo"), new LenientJsonArrayPartialMatcher(), new LenientJsonObjectPartialMatcher(), new StrictPrimitivePartialMatcher() diff --git a/src/test/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcherTest.java b/src/test/java/com/deblock/jsondiff/matcher/IgnoredPathMatcherTest.java similarity index 84% rename from src/test/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcherTest.java rename to src/test/java/com/deblock/jsondiff/matcher/IgnoredPathMatcherTest.java index 632420f..13ef93f 100644 --- a/src/test/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcherTest.java +++ b/src/test/java/com/deblock/jsondiff/matcher/IgnoredPathMatcherTest.java @@ -10,14 +10,14 @@ import static org.junit.jupiter.api.Assertions.*; -class IgnoringFieldMatcherTest { +class IgnoredPathMatcherTest { @Nested class ManageMethod { @Test void shouldMatchExactPath() { - var matcher = new IgnoringFieldMatcher("name"); + var matcher = new IgnoredPathMatcher("name"); var path = Path.ROOT.add(Path.PathItem.of("name")); assertTrue(matcher.manage(path, null, null)); @@ -25,7 +25,7 @@ void shouldMatchExactPath() { @Test void shouldMatchEndOfPath() { - var matcher = new IgnoringFieldMatcher("name"); + var matcher = new IgnoredPathMatcher("name"); var path = Path.ROOT .add(Path.PathItem.of("user")) .add(Path.PathItem.of("name")); @@ -35,7 +35,7 @@ void shouldMatchEndOfPath() { @Test void shouldNotMatchDifferentPath() { - var matcher = new IgnoringFieldMatcher("name"); + var matcher = new IgnoredPathMatcher("name"); var path = Path.ROOT.add(Path.PathItem.of("age")); assertFalse(matcher.manage(path, null, null)); @@ -43,7 +43,7 @@ void shouldNotMatchDifferentPath() { @Test void shouldMatchNestedPath() { - var matcher = new IgnoringFieldMatcher("user.name"); + var matcher = new IgnoredPathMatcher("user.name"); var path = Path.ROOT .add(Path.PathItem.of("data")) .add(Path.PathItem.of("user")) @@ -54,7 +54,7 @@ void shouldMatchNestedPath() { @Test void shouldNotMatchPartialNestedPath() { - var matcher = new IgnoringFieldMatcher("user.name"); + var matcher = new IgnoredPathMatcher("user.name"); var path = Path.ROOT .add(Path.PathItem.of("name")); @@ -63,7 +63,7 @@ void shouldNotMatchPartialNestedPath() { @Test void shouldMatchWithWildcardProperty() { - var matcher = new IgnoringFieldMatcher("*.name"); + var matcher = new IgnoredPathMatcher("*.name"); var path = Path.ROOT .add(Path.PathItem.of("user")) .add(Path.PathItem.of("name")); @@ -73,7 +73,7 @@ void shouldMatchWithWildcardProperty() { @Test void shouldMatchArrayIndex() { - var matcher = new IgnoringFieldMatcher("items[0]"); + var matcher = new IgnoredPathMatcher("items[0]"); var path = Path.ROOT .add(Path.PathItem.of("items")) .add(Path.PathItem.of(0)); @@ -83,7 +83,7 @@ void shouldMatchArrayIndex() { @Test void shouldMatchArrayWildcard() { - var matcher = new IgnoringFieldMatcher("items[*].id"); + var matcher = new IgnoredPathMatcher("items[*].id"); var path = Path.ROOT .add(Path.PathItem.of("items")) .add(Path.PathItem.of(5)) @@ -94,7 +94,7 @@ void shouldMatchArrayWildcard() { @Test void shouldMatchAnyOfMultiplePatterns() { - var matcher = new IgnoringFieldMatcher("name", "age", "email"); + var matcher = new IgnoredPathMatcher("name", "age", "email"); var pathName = Path.ROOT.add(Path.PathItem.of("name")); var pathAge = Path.ROOT.add(Path.PathItem.of("age")); var pathEmail = Path.ROOT.add(Path.PathItem.of("email")); @@ -108,7 +108,7 @@ void shouldMatchAnyOfMultiplePatterns() { @Test void shouldWorkWithListConstructor() { - var matcher = new IgnoringFieldMatcher(List.of("name", "age")); + var matcher = new IgnoredPathMatcher(List.of("name", "age")); var pathName = Path.ROOT.add(Path.PathItem.of("name")); var pathAge = Path.ROOT.add(Path.PathItem.of("age")); @@ -118,7 +118,7 @@ void shouldWorkWithListConstructor() { @Test void shouldNotMatchRootPath() { - var matcher = new IgnoringFieldMatcher("name"); + var matcher = new IgnoredPathMatcher("name"); assertFalse(matcher.manage(Path.ROOT, null, null)); } @@ -129,7 +129,7 @@ class JsonDiffMethod { @Test void shouldReturnMatchedDiffWithFullSimilarity() { - var matcher = new IgnoringFieldMatcher("name"); + var matcher = new IgnoredPathMatcher("name"); var path = Path.ROOT.add(Path.PathItem.of("name")); var expected = StringNode.valueOf("John"); var received = StringNode.valueOf("Jane"); @@ -142,7 +142,7 @@ void shouldReturnMatchedDiffWithFullSimilarity() { @Test void shouldReturnMatchedDiffEvenWithDifferentTypes() { - var matcher = new IgnoringFieldMatcher("value"); + var matcher = new IgnoredPathMatcher("value"); var path = Path.ROOT.add(Path.PathItem.of("value")); var expected = StringNode.valueOf("100"); var received = IntNode.valueOf(100); @@ -154,7 +154,7 @@ void shouldReturnMatchedDiffEvenWithDifferentTypes() { @Test void shouldReturnMatchedDiffForNestedPath() { - var matcher = new IgnoringFieldMatcher("user.timestamp"); + var matcher = new IgnoredPathMatcher("user.timestamp"); var path = Path.ROOT .add(Path.PathItem.of("user")) .add(Path.PathItem.of("timestamp"));