Add SystemTextJsonMatchers (#1447)

* SystemTextJsonMatcher

* ,

* .

* new projectx

* Update test/WireMock.Net.Tests/Pact/PactTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net/WireMock.Net.csproj

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net.Minimal/Properties/AssemblyInfo.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* .

* more tests

* .

* .

* x

* ...

* .

* fix tests

* 0.11.0

* .

* delete jsonutils.cs

* s

* fix findings

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Potential fix for pull request finding 'Missing Dispose call on local IDisposable'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* JsonConverter 0.12.0

* -- tools

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
This commit is contained in:
Stef Heyenrath
2026-05-29 20:19:51 +02:00
committed by GitHub
parent fed1c87663
commit 36b0a93a6c
68 changed files with 4202 additions and 976 deletions
@@ -1,55 +1,31 @@
// Copyright © WireMock.Net
using JsonConverter.Abstractions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
using System.Text.Json;
#endif
using JsonConverter.Abstractions.Models;
namespace WireMock.Serialization;
internal class MappingSerializer(IJsonConverter jsonConverter)
{
private static readonly JsonConverterOptions JsonConverterOptions = new JsonConverterOptions
{
DateParseHandling = (int) DateParseHandling.None
};
internal T[] DeserializeJsonToArray<T>(string value)
{
// DeserializeObject
return DeserializeObjectToArray<T>(jsonConverter.Deserialize<object>(value, JsonConverterOptions)!);
switch (JsonTypeHelper.GetJsonType(value))
{
case JsonType.Array:
return jsonConverter.Deserialize<T[]>(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone)!;
case JsonType.Object:
var singleResult = jsonConverter.Deserialize<T>(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
return [singleResult!];
default:
throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
}
}
internal static T[] DeserializeObjectToArray<T>(object value)
internal T[] DeserializeObjectToArray<T>(object value)
{
if (value is JArray jArray)
{
return jArray.ToObject<T[]>()!;
}
if (value is JObject jObject)
{
var singleResult = jObject.ToObject<T>();
return [singleResult!];
}
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
if (value is JsonElement jElement)
{
if (jElement.ValueKind == JsonValueKind.Array)
{
return jElement.Deserialize<T[]>()!;
}
if (jElement.ValueKind == JsonValueKind.Object)
{
var singleResult = jElement.Deserialize<T>();
return [singleResult!];
}
}
#endif
throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
var json = jsonConverter.Serialize(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
return DeserializeJsonToArray<T>(json);
}
}
@@ -106,9 +106,29 @@ internal class MatcherMapper
var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex);
case nameof(SystemTextJsonMatcher):
var valueForSystemTextJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new SystemTextJsonMatcher(matchBehaviour, valueForSystemTextJsonMatcher!, ignoreCase, useRegex);
case nameof(SystemTextJsonPartialMatcher):
var valueForSystemTextJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new SystemTextJsonPartialMatcher(matchBehaviour, valueForSystemTextJsonPartialMatcher!, ignoreCase, useRegex);
case nameof(SystemTextJsonPartialWildcardMatcher):
var valueForSystemTextJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new SystemTextJsonPartialWildcardMatcher(matchBehaviour, valueForSystemTextJsonPartialWildcardMatcher!, ignoreCase, useRegex);
case nameof(JsonPathMatcher):
return new JsonPathMatcher(matchBehaviour, matchOperator, stringPatterns);
case "SystemTextJsonPathMatcher":
if (TypeLoader.TryLoadNewInstance<ISystemTextJsonPathMatcher>(out var systemTextJsonPathMatcher, matchBehaviour, matchOperator, stringPatterns))
{
return systemTextJsonPathMatcher;
}
throw new InvalidOperationException("The 'SystemTextJsonPathMatcher' cannot be loaded. Please install the WireMock.Net.Matchers.SystemTextJsonPath package.");
case nameof(JmesPathMatcher):
return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns);
@@ -171,6 +191,10 @@ internal class MatcherMapper
model.Regex = jsonMatcher.Regex;
break;
case SystemTextJsonMatcher stjMatcher:
model.Regex = stjMatcher.Regex;
break;
case XPathMatcher xpathMatcher:
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
break;
@@ -1,6 +1,8 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using WireMock.Admin.Mappings;
using WireMock.Extensions;
using WireMock.Pact.Models.V2;
@@ -49,7 +51,7 @@ internal static class PactMapper
pact.Interactions.Add(interaction);
}
return (filename, JsonUtils.SerializeAsPactFile(pact));
return (filename, SerializeAsPactFile(pact));
}
private static PactRequest MapRequest(RequestModel request, string path)
@@ -152,7 +154,7 @@ internal static class PactMapper
/// </summary>
private static object? TryDeserializeJsonStringAsObject(string? value)
{
return value != null ? JsonUtils.TryDeserializeObject<object?>(value) ?? value : null;
return value != null ? TryDeserializeObject<object?>(value) ?? value : null;
}
//private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue)
@@ -164,4 +166,22 @@ internal static class PactMapper
// return defaultValue;
//}
private static byte[] SerializeAsPactFile(object value)
{
var json = JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsPact);
return Encoding.UTF8.GetBytes(json);
}
private static T? TryDeserializeObject<T>(string json)
{
try
{
return JsonConvert.DeserializeObject<T>(json);
}
catch
{
return default;
}
}
}
@@ -1,7 +1,8 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using NJsonSchema.Extensions;
using NSwag;
@@ -281,7 +282,7 @@ internal static class SwaggerMapper
if (matcher is { Name: nameof(JsonMatcher) })
{
var pattern = GetPatternAsStringFromMatcher(matcher);
if (JsonUtils.TryParseAsJObject(pattern, out var jObject))
if (TryParseAsJObject(pattern, out var jObject))
{
return jObject;
}
@@ -292,6 +293,39 @@ internal static class SwaggerMapper
return null;
}
private static bool IsJson(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
value = value!.Trim();
return (value.StartsWith("{") && value.EndsWith("}")) || (value.StartsWith("[") && value.EndsWith("]"));
}
private static bool TryParseAsJObject(string? strInput, [NotNullWhen(true)] out JObject? value)
{
value = null;
if (!IsJson(strInput))
{
return false;
}
try
{
// Try to convert this string into a JObject
value = JObject.Parse(strInput!);
return true;
}
catch
{
return false;
}
}
private static string GetContentType(RequestModel request)
{
var contentType = request.Headers?.FirstOrDefault(h => h.Name == "Content-Type");