diff --git a/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs index 1957e497..b6a450c3 100644 --- a/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs @@ -32,6 +32,11 @@ public class JsonMatcher : IJsonMatcher /// public bool Regex { get; } + /// + /// Ignore array order when comparing + /// + public bool IgnoreArrayOrder { get; } + private readonly JToken _valueAsJToken; /// @@ -40,7 +45,8 @@ public class JsonMatcher : IJsonMatcher /// The string value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). /// Support Regex. - public JsonMatcher(string value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) + /// Ignore array element order when comparing. + public JsonMatcher(string value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder) { } @@ -50,7 +56,8 @@ public class JsonMatcher : IJsonMatcher /// The object value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). /// Support Regex. - public JsonMatcher(object value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) + /// Ignore array element order when comparing. + public JsonMatcher(object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder) { } @@ -61,13 +68,15 @@ public class JsonMatcher : IJsonMatcher /// The value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). /// Support Regex. - public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) + /// Ignore array element order when comparing. + public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) { Guard.NotNull(value); MatchBehaviour = matchBehaviour; IgnoreCase = ignoreCase; Regex = regex; + IgnoreArrayOrder = ignoreArrayOrder; Value = value; _valueAsJToken = ConvertValueToJToken(value); @@ -106,7 +115,8 @@ public class JsonMatcher : IJsonMatcher $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" + + $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreArrayOrder)}" + $")"; } @@ -183,6 +193,13 @@ public class JsonMatcher : IJsonMatcher return false; } + if (IgnoreArrayOrder) + { + // Sort both arrays by their string representation and compare + valueArray = valueArray.OrderBy(t => t.ToString()).ToArray(); + inputArray = inputArray.OrderBy(t => t.ToString()).ToArray(); + } + return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any(); default: diff --git a/src/WireMock.Net.Minimal/Matchers/SystemTextJsonMatcher.cs b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonMatcher.cs index 1f472343..4176460f 100644 --- a/src/WireMock.Net.Minimal/Matchers/SystemTextJsonMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonMatcher.cs @@ -35,6 +35,11 @@ public class SystemTextJsonMatcher : IJsonMatcher /// public bool Regex { get; } + /// + /// Ignore array order when comparing + /// + public bool IgnoreArrayOrder { get; } + private readonly JsonElement _valueAsJsonElement; /// @@ -43,8 +48,9 @@ public class SystemTextJsonMatcher : IJsonMatcher /// The string value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). /// Support Regex. - public SystemTextJsonMatcher(string value, bool ignoreCase = false, bool regex = false) - : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) + /// Ignore array element order when comparing. + public SystemTextJsonMatcher(string value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) + : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder) { } @@ -54,8 +60,9 @@ public class SystemTextJsonMatcher : IJsonMatcher /// The object value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). /// Support Regex. - public SystemTextJsonMatcher(object value, bool ignoreCase = false, bool regex = false) - : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) + /// Ignore array element order when comparing. + public SystemTextJsonMatcher(object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) + : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder) { } @@ -66,13 +73,15 @@ public class SystemTextJsonMatcher : IJsonMatcher /// The value to check for equality. /// Ignore the case from the PropertyName and PropertyValue (string only). /// Support Regex. - public SystemTextJsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) + /// Ignore array element order when comparing. + public SystemTextJsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) { Guard.NotNull(value); MatchBehaviour = matchBehaviour; IgnoreCase = ignoreCase; Regex = regex; + IgnoreArrayOrder = ignoreArrayOrder; Value = value; _valueAsJsonElement = ConvertToJsonElement(value); @@ -111,7 +120,8 @@ public class SystemTextJsonMatcher : IJsonMatcher $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" + + $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreArrayOrder)}" + $")"; } @@ -202,6 +212,13 @@ public class SystemTextJsonMatcher : IJsonMatcher return false; } + if (IgnoreArrayOrder) + { + // Sort both arrays by their string representation and compare + valueArray = valueArray.OrderBy(e => e.GetRawText()).ToArray(); + inputArray = inputArray.OrderBy(e => e.GetRawText()).ToArray(); + } + return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any(); } diff --git a/test/WireMock.Net.Tests/Matchers/JsonMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/JsonMatcherTests.cs index 00cbfb4b..72309daf 100644 --- a/test/WireMock.Net.Tests/Matchers/JsonMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/JsonMatcherTests.cs @@ -523,4 +523,134 @@ public class JsonMatcherTests // Assert Assert.Equal(1.0, score); } + + [Fact] + public void JsonMatcher_IsMatch_Array_WithIgnoreArrayOrderTrue_DifferentOrder_ShouldMatch() + { + // Assign + var matcher = new JsonMatcher(new[] { "a", "b", "c" }, ignoreArrayOrder: true); + + // Act + var jArray = new JArray + { + "c", + "a", + "b" + }; + var score = matcher.IsMatch(jArray).Score; + + // Assert + Assert.Equal(1.0, score); + } + + [Fact] + public void JsonMatcher_IsMatch_Array_WithIgnoreArrayOrderFalse_DifferentOrder_ShouldNotMatch() + { + // Assign + var matcher = new JsonMatcher(new[] { "a", "b", "c" }, ignoreArrayOrder: false); + + // Act + var jArray = new JArray + { + "c", + "a", + "b" + }; + var score = matcher.IsMatch(jArray).Score; + + // Assert + Assert.Equal(MatchScores.Mismatch, score); + } + + [Fact] + public void JsonMatcher_IsMatch_Array_WithIgnoreArrayOrderTrue_SameOrder_ShouldMatch() + { + // Assign + var matcher = new JsonMatcher(new[] { "a", "b", "c" }, ignoreArrayOrder: true); + + // Act + var jArray = new JArray + { + "a", + "b", + "c" + }; + var score = matcher.IsMatch(jArray).Score; + + // Assert + Assert.Equal(1.0, score); + } + + [Fact] + public void JsonMatcher_IsMatch_Array_WithIgnoreArrayOrderTrue_DifferentLength_ShouldNotMatch() + { + // Assign + var matcher = new JsonMatcher(new[] { "a", "b", "c" }, ignoreArrayOrder: true); + + // Act + var jArray = new JArray + { + "a", + "b" + }; + var score = matcher.IsMatch(jArray).Score; + + // Assert + Assert.Equal(MatchScores.Mismatch, score); + } + + [Fact] + public void JsonMatcher_IsMatch_ObjectWithArray_WithIgnoreArrayOrderTrue_DifferentOrder_ShouldMatch() + { + // Assign + var matcher = new JsonMatcher(new { Items = new[] { "x", "y", "z" } }, ignoreArrayOrder: true); + + // Act + var jObject = new JObject + { + { "Items", new JArray("z", "x", "y") } + }; + var score = matcher.IsMatch(jObject).Score; + + // Assert + Assert.Equal(1.0, score); + } + + [Fact] + public void JsonMatcher_IsMatch_ArrayAsString_WithIgnoreArrayOrderTrue_DifferentOrder_ShouldMatch() + { + // Assign + var matcher = new JsonMatcher("[ \"a\", \"b\", \"c\" ]", ignoreArrayOrder: true); + + // Act + var jArray = new JArray + { + "c", + "b", + "a" + }; + var score = matcher.IsMatch(jArray).Score; + + // Assert + Assert.Equal(1.0, score); + } + + [Fact] + public void JsonMatcher_IsMatch_ArrayOfNumbers_WithIgnoreArrayOrderTrue_DifferentOrder_ShouldMatch() + { + // Assign + var matcher = new JsonMatcher(new[] { 1, 2, 3 }, ignoreArrayOrder: true); + + // Act + var jArray = new JArray + { + 3, + 1, + 2 + }; + var score = matcher.IsMatch(jArray).Score; + + // Assert + Assert.Equal(1.0, score); + } } diff --git a/test/WireMock.Net.Tests/Matchers/SystemTextJsonMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/SystemTextJsonMatcherTests.cs index 6a97284a..c8caea28 100644 --- a/test/WireMock.Net.Tests/Matchers/SystemTextJsonMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/SystemTextJsonMatcherTests.cs @@ -367,4 +367,101 @@ public class SystemTextJsonMatcherTests // Assert Assert.Equal(1.0, match); } -} + + [Fact] + public void SystemTextJsonMatcher_IsMatch_Array_WithIgnoreArrayOrderTrue_DifferentOrder_ShouldMatch() + { + // Assign + var matcher = new SystemTextJsonMatcher(new[] { "a", "b", "c" }, ignoreArrayOrder: true); + + // Act + var jsonElement = JsonDocument.Parse("[ \"c\", \"a\", \"b\" ]").RootElement; + var score = matcher.IsMatch(jsonElement).Score; + + // Assert + Assert.Equal(1.0, score); + } + + [Fact] + public void SystemTextJsonMatcher_IsMatch_Array_WithIgnoreArrayOrderFalse_DifferentOrder_ShouldNotMatch() + { + // Assign + var matcher = new SystemTextJsonMatcher(new[] { "a", "b", "c" }, ignoreArrayOrder: false); + + // Act + var jsonElement = JsonDocument.Parse("[ \"c\", \"a\", \"b\" ]").RootElement; + var score = matcher.IsMatch(jsonElement).Score; + + // Assert + Assert.Equal(MatchScores.Mismatch, score); + } + + [Fact] + public void SystemTextJsonMatcher_IsMatch_Array_WithIgnoreArrayOrderTrue_SameOrder_ShouldMatch() + { + // Assign + var matcher = new SystemTextJsonMatcher(new[] { "a", "b", "c" }, ignoreArrayOrder: true); + + // Act + var jsonElement = JsonDocument.Parse("[ \"a\", \"b\", \"c\" ]").RootElement; + var score = matcher.IsMatch(jsonElement).Score; + + // Assert + Assert.Equal(1.0, score); + } + + [Fact] + public void SystemTextJsonMatcher_IsMatch_Array_WithIgnoreArrayOrderTrue_DifferentLength_ShouldNotMatch() + { + // Assign + var matcher = new SystemTextJsonMatcher(new[] { "a", "b", "c" }, ignoreArrayOrder: true); + + // Act + var jsonElement = JsonDocument.Parse("[ \"a\", \"b\" ]").RootElement; + var score = matcher.IsMatch(jsonElement).Score; + + // Assert + Assert.Equal(MatchScores.Mismatch, score); + } + + [Fact] + public void SystemTextJsonMatcher_IsMatch_ObjectWithArray_WithIgnoreArrayOrderTrue_DifferentOrder_ShouldMatch() + { + // Assign + var matcher = new SystemTextJsonMatcher(new { Items = new[] { "x", "y", "z" } }, ignoreArrayOrder: true); + + // Act + var match = matcher.IsMatch("{ \"Items\" : [ \"z\", \"x\", \"y\" ] }").Score; + + // Assert + Assert.Equal(1.0, match); + } + + [Fact] + public void SystemTextJsonMatcher_IsMatch_ArrayAsString_WithIgnoreArrayOrderTrue_DifferentOrder_ShouldMatch() + { + // Assign + var matcher = new SystemTextJsonMatcher("[ \"a\", \"b\", \"c\" ]", ignoreArrayOrder: true); + + // Act + var jsonElement = JsonDocument.Parse("[ \"c\", \"b\", \"a\" ]").RootElement; + var score = matcher.IsMatch(jsonElement).Score; + + // Assert + Assert.Equal(1.0, score); + } + + [Fact] + public void SystemTextJsonMatcher_IsMatch_ArrayOfNumbers_WithIgnoreArrayOrderTrue_DifferentOrder_ShouldMatch() + { + // Assign + var matcher = new SystemTextJsonMatcher(new[] { 1, 2, 3 }, ignoreArrayOrder: true); + + // Act + var jsonElement = JsonDocument.Parse("[ 3, 1, 2 ]").RootElement; + var score = matcher.IsMatch(jsonElement).Score; + + // Assert + Assert.Equal(1.0, score); + } +} \ No newline at end of file