diff --git a/src/WireMock.Net.Minimal/Matchers/AbstractJsonPartialMatcher.cs b/src/WireMock.Net.Minimal/Matchers/AbstractJsonPartialMatcher.cs
index f2f853cb..f1e25ccc 100644
--- a/src/WireMock.Net.Minimal/Matchers/AbstractJsonPartialMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/AbstractJsonPartialMatcher.cs
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
-using System.Linq;
using Newtonsoft.Json.Linq;
using WireMock.Util;
diff --git a/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs
index 64a04733..51635dac 100644
--- a/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs
+++ b/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
-using System.Linq;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Extensions;
diff --git a/src/WireMock.Net.Minimal/Matchers/SystemTextJsonMatcher.cs b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonMatcher.cs
new file mode 100644
index 00000000..1f472343
--- /dev/null
+++ b/src/WireMock.Net.Minimal/Matchers/SystemTextJsonMatcher.cs
@@ -0,0 +1,282 @@
+// Copyright © WireMock.Net
+
+using System.Collections;
+using System.Text.Json;
+using Stef.Validation;
+using WireMock.Extensions;
+using WireMock.Util;
+
+namespace WireMock.Matchers;
+
+///
+/// SystemTextJsonMatcher - behaves the same as but uses System.Text.Json instead of Newtonsoft.Json.
+///
+public class SystemTextJsonMatcher : IJsonMatcher
+{
+ private static readonly JsonSerializerOptions DefaultSerializerOptions = new()
+ {
+ PropertyNameCaseInsensitive = false
+ };
+
+ ///
+ public virtual string Name => nameof(SystemTextJsonMatcher);
+
+ ///
+ public object Value { get; }
+
+ ///
+ public MatchBehaviour MatchBehaviour { get; }
+
+ ///
+ public bool IgnoreCase { get; }
+
+ ///
+ /// Support Regex
+ ///
+ public bool Regex { get; }
+
+ private readonly JsonElement _valueAsJsonElement;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// 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)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// 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)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The match behaviour.
+ /// 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)
+ {
+ Guard.NotNull(value);
+
+ MatchBehaviour = matchBehaviour;
+ IgnoreCase = ignoreCase;
+ Regex = regex;
+
+ Value = value;
+ _valueAsJsonElement = ConvertToJsonElement(value);
+ }
+
+ ///
+ public MatchResult IsMatch(object? input)
+ {
+ var score = MatchScores.Mismatch;
+ Exception? error = null;
+
+ // When input is null or byte[], return Mismatch.
+ if (input != null && input is not byte[])
+ {
+ try
+ {
+ var inputAsJsonElement = ConvertToJsonElement(input);
+
+ var match = IsMatch(NormalizeElement(_valueAsJsonElement), NormalizeElement(inputAsJsonElement));
+ score = MatchScores.ToScore(match);
+ }
+ catch (Exception ex)
+ {
+ error = ex;
+ }
+ }
+
+ return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
+ }
+
+ ///
+ public virtual string GetCSharpCodeArguments()
+ {
+ return $"new {Name}" +
+ $"(" +
+ $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
+ $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
+ $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
+ $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
+ $")";
+ }
+
+ ///
+ /// Compares the input against the matcher value
+ ///
+ protected virtual bool IsMatch(JsonElement value, JsonElement? input)
+ {
+ if (input == null)
+ {
+ return false;
+ }
+
+ var inputElement = input.Value;
+
+ // If using Regex and the value is a string, use the MatchRegex method.
+ if (Regex && value.ValueKind == JsonValueKind.String)
+ {
+ var valueAsString = value.GetString()!;
+ var inputAsString = inputElement.ValueKind == JsonValueKind.String
+ ? inputElement.GetString()!
+ : inputElement.GetRawText();
+
+ var (valid, result) = RegexUtils.MatchRegex(valueAsString, inputAsString);
+ if (valid)
+ {
+ return result;
+ }
+ }
+
+ // If the value is a Guid (string) and input is a string, or vice versa, compare as strings.
+ if (value.ValueKind == JsonValueKind.String && inputElement.ValueKind == JsonValueKind.String)
+ {
+ var valueStr = value.GetString()!;
+ var inputStr = inputElement.GetString()!;
+
+ if (Guid.TryParse(valueStr, out var valueGuid) && Guid.TryParse(inputStr, out var inputGuid))
+ {
+ return valueGuid == inputGuid;
+ }
+ }
+
+ switch (value.ValueKind)
+ {
+ case JsonValueKind.Object:
+ {
+ if (inputElement.ValueKind != JsonValueKind.Object)
+ {
+ return false;
+ }
+
+ var valueProperties = value.EnumerateObject().ToDictionary(p => p.Name, p => p.Value);
+ var inputProperties = inputElement.EnumerateObject().ToDictionary(p => p.Name, p => p.Value);
+
+ if (valueProperties.Count != inputProperties.Count)
+ {
+ return false;
+ }
+
+ foreach (var pair in valueProperties)
+ {
+ if (!inputProperties.TryGetValue(pair.Key, out var inputPropValue))
+ {
+ return false;
+ }
+
+ if (!IsMatch(pair.Value, inputPropValue))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ case JsonValueKind.Array:
+ {
+ if (inputElement.ValueKind != JsonValueKind.Array)
+ {
+ return false;
+ }
+
+ var valueArray = value.EnumerateArray().ToArray();
+ var inputArray = inputElement.EnumerateArray().ToArray();
+
+ if (valueArray.Length != inputArray.Length)
+ {
+ return false;
+ }
+
+ return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any();
+ }
+
+ default:
+ return value.GetRawText() == inputElement.GetRawText();
+ }
+ }
+
+ private JsonElement NormalizeElement(JsonElement element)
+ {
+ if (!IgnoreCase)
+ {
+ return element;
+ }
+
+ var normalized = NormalizeValue(element);
+ return ConvertToJsonElement(normalized);
+ }
+
+ private object NormalizeValue(JsonElement element)
+ {
+ switch (element.ValueKind)
+ {
+ case JsonValueKind.Object:
+ {
+ var dict = new Dictionary();
+ foreach (var prop in element.EnumerateObject())
+ {
+ var normalizedKey = prop.Name.ToUpperInvariant();
+ dict[normalizedKey] = NormalizeValue(prop.Value);
+ }
+
+ return dict;
+ }
+
+ case JsonValueKind.Array:
+ {
+ if (Regex)
+ {
+ return element.EnumerateArray().Select(e => (object)e.GetRawText()).ToArray();
+ }
+
+ return element.EnumerateArray().Select(NormalizeValue).ToArray();
+ }
+
+ case JsonValueKind.String:
+ {
+ var str = element.GetString()!;
+ return Regex ? str : str.ToUpperInvariant();
+ }
+
+ default:
+ return element.GetRawText();
+ }
+ }
+
+ private static JsonElement ConvertToJsonElement(object value)
+ {
+ switch (value)
+ {
+ case JsonElement jsonElement:
+ return jsonElement;
+
+ case JsonDocument jsonDocument:
+ return jsonDocument.RootElement;
+
+ case string stringValue:
+ return JsonDocument.Parse(stringValue).RootElement;
+
+ case IEnumerable enumerableValue when value is not string:
+ return JsonSerializer.SerializeToElement(enumerableValue, DefaultSerializerOptions);
+
+ default:
+ var json = JsonSerializer.Serialize(value, DefaultSerializerOptions);
+ return JsonDocument.Parse(json).RootElement;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/Matchers/SystemTextJsonMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/SystemTextJsonMatcherTests.cs
new file mode 100644
index 00000000..cbc7ba73
--- /dev/null
+++ b/test/WireMock.Net.Tests/Matchers/SystemTextJsonMatcherTests.cs
@@ -0,0 +1,369 @@
+// Copyright © WireMock.Net
+
+using System.Text.Json;
+using WireMock.Matchers;
+
+namespace WireMock.Net.Tests.Matchers;
+
+public class SystemTextJsonMatcherTests
+{
+ public enum NormalEnumStj
+ {
+ Abc
+ }
+
+ public class Test1Stj
+ {
+ public NormalEnumStj NormalEnum { get; set; }
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_GetName()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher("{}");
+
+ // Act
+ var name = matcher.Name;
+
+ // Assert
+ name.Should().Be("SystemTextJsonMatcher");
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_GetValue()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher("{}");
+
+ // Act
+ var value = matcher.Value;
+
+ // Assert
+ value.Should().Be("{}");
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_WithInvalidStringValue_Should_ThrowException()
+ {
+ // Act
+ Action action = () => new SystemTextJsonMatcher(MatchBehaviour.AcceptOnMatch, "{ \"Id\"");
+
+ // Assert
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_WithInvalidObjectValue_Should_ThrowException()
+ {
+ // Act
+ Action action = () => new SystemTextJsonMatcher(MatchBehaviour.AcceptOnMatch, new MemoryStream());
+
+ // Assert
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_WithInvalidValue_Should_ReturnMismatch_And_Exception_ShouldBeSet()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher("{}");
+
+ // Act
+ var result = matcher.IsMatch(new MemoryStream());
+
+ // Assert
+ result.Score.Should().Be(MatchScores.Mismatch);
+ result.Exception.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_ByteArray()
+ {
+ // Assign
+ var bytes = new byte[0];
+ var matcher = new SystemTextJsonMatcher("{}");
+
+ // Act
+ var match = matcher.IsMatch(bytes).Score;
+
+ // Assert
+ match.Should().Be(0);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_NullString()
+ {
+ // Assign
+ string? s = null;
+ var matcher = new SystemTextJsonMatcher("{}");
+
+ // Act
+ var match = matcher.IsMatch(s).Score;
+
+ // Assert
+ match.Should().Be(0);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_NullObject()
+ {
+ // Assign
+ object? o = null;
+ var matcher = new SystemTextJsonMatcher("{}");
+
+ // Act
+ var match = matcher.IsMatch(o).Score;
+
+ // Assert
+ match.Should().Be(0);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_JsonArrayAsString()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher("[ \"x\", \"y\" ]");
+
+ // Act
+ var jsonElement = JsonDocument.Parse("[ \"x\", \"y\" ]").RootElement;
+ var match = matcher.IsMatch(jsonElement).Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_JsonObjectAsString_ShouldMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher("{ \"Id\" : 1, \"Name\" : \"Test\" }");
+
+ // Act
+ var jsonElement = JsonDocument.Parse("{ \"Id\" : 1, \"Name\" : \"Test\" }").RootElement;
+ var match = matcher.IsMatch(jsonElement).Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_AnonymousObject_ShouldMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new { Id = 1, Name = "Test" });
+
+ // Act
+ var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_AnonymousObject_ShouldNotMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new { Id = 1, Name = "Test" });
+
+ // Act
+ var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\", \"Other\" : \"abc\" }").Score;
+
+ // Assert
+ Assert.Equal(MatchScores.Mismatch, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_WithIgnoreCaseTrue_JsonObject()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new { id = 1, Name = "test" }, true);
+
+ // Act
+ var match = matcher.IsMatch("{ \"Id\" : 1, \"NaMe\" : \"Test\" }").Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_WithIgnoreCaseTrue_JsonObjectParsed()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new { Id = 1, Name = "TESt" }, true);
+
+ // Act
+ var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_JsonObjectAsString_RejectOnMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(MatchBehaviour.RejectOnMatch, "{ \"Id\" : 1, \"Name\" : \"Test\" }");
+
+ // Act
+ var match = matcher.IsMatch("{ \"Id\" : 1, \"Name\" : \"Test\" }").Score;
+
+ // Assert
+ Assert.Equal(0.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_JsonObjectWithDateTimeOffsetAsString()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher("{ \"preferredAt\" : \"2019-11-21T10:32:53.2210009+00:00\" }");
+
+ // Act
+ var match = matcher.IsMatch("{ \"preferredAt\" : \"2019-11-21T10:32:53.2210009+00:00\" }").Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_NormalEnum()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new Test1Stj { NormalEnum = NormalEnumStj.Abc });
+
+ // Act
+ var match = matcher.IsMatch("{ \"NormalEnum\" : 0 }").Score;
+
+ // Assert
+ match.Should().Be(1.0);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_WithRegexTrue_ShouldMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new { Id = "^\\d+$", Name = "Test" }, regex: true);
+
+ // Act
+ var match = matcher.IsMatch("{ \"Id\" : \"42\", \"Name\" : \"Test\" }").Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_WithRegexTrue_Complex_ShouldMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new
+ {
+ Complex = new
+ {
+ Id = "^\\d+$",
+ Name = ".*"
+ }
+ }, regex: true);
+
+ // Act
+ var match = matcher.IsMatch("{ \"Complex\" : { \"Id\" : \"42\", \"Name\" : \"Test\" } }").Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_WithRegexTrue_Complex_ShouldNotMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new
+ {
+ Complex = new
+ {
+ Id = "^\\d+$",
+ Name = ".*"
+ }
+ }, regex: true);
+
+ // Act
+ var match = matcher.IsMatch("{ \"Complex\" : { \"Id\" : \"42\", \"Name\" : \"Test\", \"Other\" : \"Other\" } }").Score;
+
+ // Assert
+ Assert.Equal(MatchScores.Mismatch, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_WithRegexTrue_Array_ShouldMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new
+ {
+ Array = new[] { "^\\d+$", ".*" }
+ }, regex: true);
+
+ // Act
+ var match = matcher.IsMatch("{ \"Array\" : [ \"42\", \"test\" ] }").Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_WithRegexTrue_Array_ShouldNotMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new
+ {
+ Array = new[] { "^\\d+$", ".*" }
+ }, regex: true);
+
+ // Act
+ var match = matcher.IsMatch("{ \"Array\" : [ \"42\", \"test\", \"other\" ] }").Score;
+
+ // Assert
+ Assert.Equal(MatchScores.Mismatch, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_GuidAndString()
+ {
+ // Assign
+ var id = Guid.NewGuid();
+ var idAsString = id.ToString();
+ var matcher = new SystemTextJsonMatcher(new { Id = id });
+
+ // Act
+ var match = matcher.IsMatch($"{{ \"Id\" : \"{idAsString}\" }}").Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_StringAndGuid()
+ {
+ // Assign
+ var id = Guid.NewGuid();
+ var idAsString = id.ToString();
+ var matcher = new SystemTextJsonMatcher(new { Id = idAsString });
+
+ // Act
+ var match = matcher.IsMatch($"{{ \"Id\" : \"{id}\" }}").Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+
+ [Fact]
+ public void SystemTextJsonMatcher_IsMatch_JsonElement_ShouldMatch()
+ {
+ // Assign
+ var matcher = new SystemTextJsonMatcher(new { Id = 1, Name = "Test" });
+
+ // Act
+ var jsonElement = JsonDocument.Parse("{ \"Id\" : 1, \"Name\" : \"Test\" }").RootElement;
+ var match = matcher.IsMatch(jsonElement).Score;
+
+ // Assert
+ Assert.Equal(1.0, match);
+ }
+}
diff --git a/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs b/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs
index d32e8517..580bfdf8 100644
--- a/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs
+++ b/test/WireMock.Net.Tests/Serialization/MatcherMapperTests.cs
@@ -523,7 +523,7 @@ message HelloReply {
};
// Act
- var matcher = (JsonMatcher)_sut.Map(model)!;
+ var matcher = (IJsonMatcher)_sut.Map(model)!;
// Assert
matcher.MatchBehaviour.Should().Be(MatchBehaviour.AcceptOnMatch);