From 8a3dba81845da5c85ab587774e3df5bc4cb59807 Mon Sep 17 00:00:00 2001 From: Thomas Deblock Date: Sun, 21 Apr 2024 18:41:54 +0200 Subject: [PATCH] feat: allow to have equality between null and empty array --- .../matcher/CompositeJsonMatcher.java | 34 +++++++------------ .../deblock/jsondiff/matcher/JsonMatcher.java | 1 - .../LenientJsonArrayPartialMatcher.java | 6 ++++ .../LenientJsonObjectPartialMatcher.java | 6 ++++ .../LenientNumberPrimitivePartialMatcher.java | 6 ++++ .../matcher/NullEqualsEmptyArrayMatcher.java | 26 ++++++++++++++ .../jsondiff/matcher/PartialJsonMatcher.java | 3 ++ .../StrictJsonArrayPartialMatcher.java | 6 ++++ .../StrictJsonObjectPartialMatcher.java | 6 ++++ .../StrictPrimitivePartialMatcher.java | 6 ++++ .../java/com/deblock/jsondiff/Sample.java | 5 +-- 11 files changed, 81 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcher.java diff --git a/src/main/java/com/deblock/jsondiff/matcher/CompositeJsonMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/CompositeJsonMatcher.java index af5e090..56b1dbd 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/CompositeJsonMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/CompositeJsonMatcher.java @@ -6,32 +6,24 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ValueNode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public class CompositeJsonMatcher implements JsonMatcher { - private final PartialJsonMatcher jsonArrayPartialMatcher; - private final PartialJsonMatcher jsonObjectPartialMatcher; - private final PartialJsonMatcher primitivePartialMatcher; + private final List> matchers; - public CompositeJsonMatcher( - PartialJsonMatcher jsonArrayPartialMatcher, - PartialJsonMatcher jsonObjectPartialMatcher, - PartialJsonMatcher primitivePartialMatcher - ) { - this.jsonArrayPartialMatcher = jsonArrayPartialMatcher; - this.jsonObjectPartialMatcher = jsonObjectPartialMatcher; - this.primitivePartialMatcher = primitivePartialMatcher; + public CompositeJsonMatcher(PartialJsonMatcher ...jsonArrayPartialMatcher) { + this.matchers = new ArrayList<>(); + Arrays.stream(jsonArrayPartialMatcher).forEach(it -> this.matchers.add((PartialJsonMatcher) it)); } @Override public JsonDiff diff(Path path, JsonNode expected, JsonNode received) { - if (expected instanceof ObjectNode && received instanceof ObjectNode) { - return this.jsonObjectPartialMatcher.jsonDiff(path, (ObjectNode) expected, (ObjectNode) received, this); - } else if (expected instanceof ArrayNode && received instanceof ArrayNode) { - return this.jsonArrayPartialMatcher.jsonDiff(path, (ArrayNode) expected, (ArrayNode) received, this); - } else if (expected instanceof ValueNode && received instanceof ValueNode){ - return this.primitivePartialMatcher.jsonDiff(path, (ValueNode) expected, (ValueNode) received, this); - } else { - return new UnMatchedPrimaryDiff(path, expected, received); - } + return this.matchers.stream() + .filter(matcher -> matcher.manage(expected, received)) + .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/JsonMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/JsonMatcher.java index cf068e8..4c7f33a 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/JsonMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/JsonMatcher.java @@ -6,5 +6,4 @@ public interface JsonMatcher { JsonDiff diff(Path path, JsonNode expected, JsonNode received); - } diff --git a/src/main/java/com/deblock/jsondiff/matcher/LenientJsonArrayPartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/LenientJsonArrayPartialMatcher.java index 5744858..22d920e 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/LenientJsonArrayPartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/LenientJsonArrayPartialMatcher.java @@ -2,6 +2,7 @@ import com.deblock.jsondiff.diff.JsonArrayDiff; import com.deblock.jsondiff.diff.JsonDiff; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import java.util.*; @@ -51,6 +52,11 @@ public JsonDiff jsonDiff(Path path, ArrayNode expectedValues, ArrayNode received return diff; } + @Override + public boolean manage(JsonNode expected, JsonNode received) { + return expected.isArray() && received.isArray(); + } + private double maxSimilarityRate(Map.Entry> entry) { return entry.getValue().values().stream().mapToDouble(JsonDiff::similarityRate).max().orElse(0); } diff --git a/src/main/java/com/deblock/jsondiff/matcher/LenientJsonObjectPartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/LenientJsonObjectPartialMatcher.java index 6b758f2..79b66ea 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/LenientJsonObjectPartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/LenientJsonObjectPartialMatcher.java @@ -2,6 +2,7 @@ import com.deblock.jsondiff.diff.JsonDiff; import com.deblock.jsondiff.diff.JsonObjectDiff; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; public class LenientJsonObjectPartialMatcher implements PartialJsonMatcher { @@ -26,4 +27,9 @@ public JsonDiff jsonDiff(Path path, ObjectNode expectedJson, ObjectNode received return jsonDiff; } + + @Override + public boolean manage(JsonNode expected, JsonNode received) { + return expected.isObject() && received.isObject(); + } } diff --git a/src/main/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcher.java index 9d1fadc..9ea8122 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/LenientNumberPrimitivePartialMatcher.java @@ -3,6 +3,7 @@ import com.deblock.jsondiff.diff.JsonDiff; import com.deblock.jsondiff.diff.MatchedPrimaryDiff; import com.deblock.jsondiff.diff.UnMatchedPrimaryDiff; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.NumericNode; import com.fasterxml.jackson.databind.node.ValueNode; @@ -30,4 +31,9 @@ public JsonDiff jsonDiff(Path path, ValueNode expectedValue, ValueNode receivedV return delegated.jsonDiff(path, expectedValue, receivedValue, jsonMatcher); } + + @Override + public boolean manage(JsonNode expected, JsonNode received) { + return expected.isValueNode() && received.isValueNode(); + } } diff --git a/src/main/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcher.java new file mode 100644 index 0000000..d9648dd --- /dev/null +++ b/src/main/java/com/deblock/jsondiff/matcher/NullEqualsEmptyArrayMatcher.java @@ -0,0 +1,26 @@ +package com.deblock.jsondiff.matcher; + +import com.deblock.jsondiff.diff.JsonDiff; +import com.deblock.jsondiff.diff.MatchedPrimaryDiff; +import com.deblock.jsondiff.diff.UnMatchedPrimaryDiff; +import com.fasterxml.jackson.databind.JsonNode; + +public class NullEqualsEmptyArrayMatcher implements PartialJsonMatcher { + + @Override + public JsonDiff jsonDiff(Path path, JsonNode expectedJson, JsonNode receivedJson, JsonMatcher jsonMatcher) { + if ( + (expectedJson.isNull() && receivedJson.isEmpty()) + || (receivedJson.isNull() && expectedJson.isEmpty()) + ) { + return new MatchedPrimaryDiff(path, expectedJson); + } + return new UnMatchedPrimaryDiff(path, expectedJson, receivedJson); + } + + @Override + public boolean manage(JsonNode expected, JsonNode received) { + 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 0a572e8..b2b24f6 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/PartialJsonMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/PartialJsonMatcher.java @@ -5,4 +5,7 @@ public interface PartialJsonMatcher { JsonDiff jsonDiff(Path path, T expectedJson, T receivedJson, JsonMatcher jsonMatcher); + + boolean manage(JsonNode expected, JsonNode received); + } diff --git a/src/main/java/com/deblock/jsondiff/matcher/StrictJsonArrayPartialMatcher.java b/src/main/java/com/deblock/jsondiff/matcher/StrictJsonArrayPartialMatcher.java index f79f8a1..99dd335 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/StrictJsonArrayPartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/StrictJsonArrayPartialMatcher.java @@ -2,6 +2,7 @@ import com.deblock.jsondiff.diff.JsonArrayDiff; import com.deblock.jsondiff.diff.JsonDiff; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import java.util.Comparator; @@ -35,4 +36,9 @@ public JsonDiff jsonDiff(Path path, ArrayNode expectedValues, ArrayNode received } return diff; } + + @Override + public boolean manage(JsonNode expected, JsonNode received) { + 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 9e38b9a..49964a5 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/StrictJsonObjectPartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/StrictJsonObjectPartialMatcher.java @@ -2,6 +2,7 @@ import com.deblock.jsondiff.diff.JsonDiff; import com.deblock.jsondiff.diff.JsonObjectDiff; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.stream.Collectors; @@ -43,4 +44,9 @@ public JsonDiff jsonDiff(Path path, ObjectNode expectedJson, ObjectNode received return jsonDiff; } + + @Override + public boolean manage(JsonNode expected, JsonNode received) { + 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 777d94d..4f3df1a 100644 --- a/src/main/java/com/deblock/jsondiff/matcher/StrictPrimitivePartialMatcher.java +++ b/src/main/java/com/deblock/jsondiff/matcher/StrictPrimitivePartialMatcher.java @@ -3,6 +3,7 @@ import com.deblock.jsondiff.diff.JsonDiff; import com.deblock.jsondiff.diff.MatchedPrimaryDiff; import com.deblock.jsondiff.diff.UnMatchedPrimaryDiff; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ValueNode; import java.util.Objects; @@ -17,4 +18,9 @@ public JsonDiff jsonDiff(Path path, ValueNode expectedValue, ValueNode receivedV return new UnMatchedPrimaryDiff(path, expectedValue, receivedValue); } } + + @Override + public boolean manage(JsonNode expected, JsonNode received) { + return expected.isValueNode() && received.isValueNode(); + } } diff --git a/src/test/java/com/deblock/jsondiff/Sample.java b/src/test/java/com/deblock/jsondiff/Sample.java index 29dffe1..3a34f33 100644 --- a/src/test/java/com/deblock/jsondiff/Sample.java +++ b/src/test/java/com/deblock/jsondiff/Sample.java @@ -6,12 +6,13 @@ public class Sample { public static void main(String[] args) { - final var expectedJson = "{\"array\": [{\"b\": [1]}, {\"a\": [1, 5]}]}"; - final var receivedJson = "{\"array\": [{\"a\": [1]}]}"; + final var expectedJson = "{\"array\": []}"; + final var receivedJson = "{}"; // define your matcher // CompositeJsonMatcher use other matcher to perform matching on objects, list or primitive final var jsonMatcher = new CompositeJsonMatcher( + new NullEqualsEmptyArrayMatcher(), new LenientJsonArrayPartialMatcher(), // comparing array using lenient mode (ignore array order and extra items) new LenientJsonObjectPartialMatcher(), // comparing object using lenient mode (ignoring extra properties) new LenientNumberPrimitivePartialMatcher(new StrictPrimitivePartialMatcher()) // comparing primitive types and manage numbers (100.00 == 100)