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
@@ -0,0 +1,11 @@
// Copyright © WireMock.Net
namespace WireMock.Matchers;
/// <summary>
/// IJsonPathMatcher
/// <seealso cref="IStringMatcher"/> and <seealso cref="IObjectMatcher"/>.
/// </summary>
public interface IJsonPathMatcher : IStringMatcher, IObjectMatcher
{
}
@@ -0,0 +1,11 @@
// Copyright © WireMock.Net
namespace WireMock.Matchers;
/// <summary>
/// ISystemTextJsonPathMatcher
/// <seealso cref="IJsonPathMatcher"/>.
/// </summary>
public interface ISystemTextJsonPathMatcher : IJsonPathMatcher
{
}
@@ -7,9 +7,10 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WireMock.Net.GraphQL, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.ProtoBuf, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.Matchers.CSharpCode, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.Matchers.SystemTextJsonPath, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.OpenTelemetry, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
// [assembly: InternalsVisibleTo("WireMock.Net.StandAlone, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
// Needed for Moq in the UnitTest project
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Text;
using System.Threading.Tasks;
using JsonConverter.Abstractions;
using WireMock.Models;
@@ -19,8 +17,10 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
/// <param name="body">The body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param>
/// <param name="jsonConverter">The JSON converter.</param>
/// <param name="options">The JSON converter options.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null, IJsonConverter? jsonConverter = null, JsonConverterOptions? options = null);
/// <summary>
/// WithBody : Create a ... response based on a callback function.
@@ -14,16 +14,16 @@ internal static class JsonSerializationConstants
IgnoreNullValues = true
};
//internal static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new()
//{
// Formatting = Formatting.Indented,
// NullValueHandling = NullValueHandling.Ignore
//};
internal static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new()
internal static readonly JsonConverterOptions JsonConverterOptionsIncludeNullValues = new()
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Include
WriteIndented = true,
IgnoreNullValues = false
};
internal static readonly JsonConverterOptions JsonConverterOptionsWithDateParsingNone = new()
{
WriteIndented = true,
DateParseHandling = 0
};
internal static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new()
@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using System.Collections;
using JsonConverter.Abstractions;
using WireMock.Extensions;
using WireMock.Util;
@@ -191,9 +192,9 @@ internal class SimpleSettingsParser
return GetValue(name, values => values.FirstOrDefault());
}
public T? GetObjectValueFromJson<T>(string name)
public T? GetObjectValueFromJson<T>(string name, IJsonConverter jsonConverter)
{
var value = GetValue(name, values => values.FirstOrDefault());
return string.IsNullOrWhiteSpace(value) ? default : JsonUtils.DeserializeObject<T>(value!);
return string.IsNullOrWhiteSpace(value) ? default : jsonConverter.Deserialize<T>(value!);
}
}
@@ -14,6 +14,7 @@ using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Models;
using WireMock.RegularExpressions;
using WireMock.Transformers;
using WireMock.Types;
namespace WireMock.Settings;
@@ -362,11 +363,31 @@ public class WireMockServerSettings
/// Default is <see cref="NewtonsoftJsonConverter"/>.
/// </remarks>
[PublicAPI]
public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter();
public IJsonConverter DefaultJsonSerializer { get; set; }
/// <summary>
/// Gets or sets the default JSON body transformer used for template-based JSON body transformations.
/// </summary>
/// <remarks>
/// Set this property to provide a custom implementation for transforming JSON and ProtoBuf body content.
/// Default is <see cref="NewtonsoftJsonBodyTransformer"/>.
/// </remarks>
[PublicAPI]
[JsonIgnore]
public IJsonBodyTransformer DefaultJsonBodyTransformer { get; set; }
/// <summary>
/// WebSocket settings.
/// </summary>
[PublicAPI]
public WebSocketSettings? WebSocketSettings { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="WireMockServerSettings"/> class.
/// </summary>
public WireMockServerSettings()
{
DefaultJsonSerializer = new NewtonsoftJsonConverter();
DefaultJsonBodyTransformer = new NewtonsoftJsonBodyTransformer(this);
}
}
@@ -0,0 +1,28 @@
// Copyright © WireMock.Net
using JetBrains.Annotations;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Transformers;
/// <summary>
/// Defines the contract for transforming JSON-like body data using a transformer context.
/// </summary>
[PublicAPI]
public interface IJsonBodyTransformer
{
/// <summary>
/// Transforms the JSON body using the provided transformer context and model.
/// </summary>
/// <param name="transformerContext">The transformer context used to render and evaluate template values.</param>
/// <param name="options">The JSON node replacement behavior to apply during transformation.</param>
/// <param name="model">The model used when rendering or evaluating template values.</param>
/// <param name="original">The original body data to transform.</param>
/// <returns>The transformed JSON body data.</returns>
BodyData TransformBodyAsJson(
ITransformerContext transformerContext,
ReplaceNodeOptions options,
object model,
IBodyData original);
}
@@ -0,0 +1,34 @@
// Copyright © WireMock.Net
using JetBrains.Annotations;
using WireMock.Handlers;
namespace WireMock.Transformers;
/// <summary>
/// Defines the transformer context used to render and evaluate templates during response transformation.
/// </summary>
[PublicAPI]
public interface ITransformerContext
{
/// <summary>
/// Gets the file system handler used by the current transformer context.
/// </summary>
IFileSystemHandler FileSystemHandler { get; }
/// <summary>
/// Renders the specified template text using the supplied model.
/// </summary>
/// <param name="text">The template text to render.</param>
/// <param name="model">The model used during rendering.</param>
/// <returns>The rendered text.</returns>
string ParseAndRender(string text, object model);
/// <summary>
/// Evaluates the specified template text using the supplied model.
/// </summary>
/// <param name="text">The template text to evaluate.</param>
/// <param name="model">The model used during evaluation.</param>
/// <returns>The evaluated value.</returns>
object? ParseAndEvaluate(string text, object model);
}
@@ -0,0 +1,271 @@
// Copyright © WireMock.Net
using System.Collections;
using HandlebarsDotNet.Helpers.Models;
using JetBrains.Annotations;
using JsonConverter.Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Transformers;
/// <summary>
/// Default JSON body transformer implementation based on Newtonsoft.Json.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="NewtonsoftJsonBodyTransformer"/> class.
/// </remarks>
/// <param name="settings">The server settings used to configure JSON transformation behavior.</param>
[PublicAPI]
public class NewtonsoftJsonBodyTransformer(WireMockServerSettings settings) : IJsonBodyTransformer
{
private static readonly NewtonsoftJsonConverter _jsonConverter = new();
/// <inheritdoc />
public BodyData TransformBodyAsJson(
ITransformerContext transformerContext,
ReplaceNodeOptions options,
object model,
IBodyData original)
{
var jsonSerializer = new JsonSerializer
{
Culture = settings.Culture
};
JToken? jToken = null;
switch (original.BodyAsJson)
{
case JObject bodyAsJObject:
jToken = bodyAsJObject.DeepClone();
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
break;
case JArray bodyAsJArray:
jToken = bodyAsJArray.DeepClone();
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
break;
case var bodyAsEnumerable when bodyAsEnumerable is IEnumerable and not string:
jToken = JArray.FromObject(bodyAsEnumerable, jsonSerializer);
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
break;
case string bodyAsString:
jToken = ReplaceSingleNode(transformerContext, jsonSerializer, options, bodyAsString, model);
break;
case not null:
jToken = JObject.FromObject(original.BodyAsJson, jsonSerializer);
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
break;
}
return new BodyData
{
Encoding = original.Encoding,
DetectedBodyType = original.DetectedBodyType,
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
ProtoDefinition = original.ProtoDefinition,
ProtoBufMessageType = original.ProtoBufMessageType,
BodyAsJson = jToken
};
}
private JToken ParseAsJObject(string stringValue)
{
if (_jsonConverter.IsValidJson(stringValue))
{
try
{
// Try to convert this string into a JObject
return JObject.Parse(stringValue!);
}
catch
{
settings.Logger.Warn("Failed to parse string ''{0}'' as JSON. Returning the original string value.", stringValue);
}
}
return stringValue;
}
private JToken ReplaceSingleNode(ITransformerContext transformerContext, JsonSerializer jsonSerializer, ReplaceNodeOptions options, string stringValue, object model)
{
var transformedString = transformerContext.ParseAndRender(stringValue, model);
if (!string.Equals(stringValue, transformedString))
{
const string property = "_";
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
if (dummy[property] == null)
{
return string.Empty;
}
JToken node = dummy[property]!;
ReplaceNodeValue(jsonSerializer, options, node, transformedString);
return dummy[property]!;
}
return stringValue;
}
private void WalkNode(ITransformerContext transformerContext, JsonSerializer jsonSerializer, ReplaceNodeOptions options, JToken node, object model)
{
switch (node.Type)
{
case JTokenType.Object:
foreach (var child in node.Children<JProperty>().ToArray())
{
WalkNode(transformerContext, jsonSerializer, options, child.Value, model);
}
break;
case JTokenType.Array:
foreach (var child in node.Children().ToArray())
{
WalkNode(transformerContext, jsonSerializer, options, child, model);
}
break;
case JTokenType.String:
var stringValue = node.Value<string>();
if (string.IsNullOrEmpty(stringValue))
{
return;
}
var transformed = transformerContext.ParseAndEvaluate(stringValue!, model);
if (!Equals(stringValue, transformed))
{
ReplaceNodeValue(jsonSerializer, options, node, transformed);
}
break;
}
}
private void ReplaceNodeValue(JsonSerializer jsonSerializer, ReplaceNodeOptions options, JToken node, object? transformedValue)
{
switch (transformedValue)
{
case JValue jValue:
node.Replace(jValue);
return;
case string transformedString:
var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString);
if (isConvertedFromString)
{
node.Replace(JToken.FromObject(convertedValueFromString, jsonSerializer));
}
else
{
node.Replace(ParseAsJObject(transformedString));
}
break;
case WireMockList<string> strings:
switch (strings.Count)
{
case 1:
node.Replace(ParseAsJObject(strings[0]));
return;
case > 1:
node.Replace(JToken.FromObject(strings.ToArray(), jsonSerializer));
return;
}
break;
case { }:
var (isConverted, convertedValue) = TryConvert(options, transformedValue);
if (isConverted)
{
node.Replace(JToken.FromObject(convertedValue, jsonSerializer));
}
return;
default:
return;
}
}
private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value)
{
var valueAsString = value as string;
if (options == ReplaceNodeOptions.Evaluate)
{
if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded))
{
return (true, decoded);
}
return (false, value);
}
if (valueAsString != null)
{
return WrappedString.TryDecode(valueAsString, out var decoded)
? (true, decoded)
: TryConvertToKnownType(valueAsString);
}
return (false, value);
}
internal static (bool IsConverted, object ConvertedValue) TryConvertToKnownType(string value)
{
if (bool.TryParse(value, out var boolResult))
{
return (true, boolResult);
}
if (int.TryParse(value, out var intResult))
{
return (true, intResult);
}
if (long.TryParse(value, out var longResult))
{
return (true, longResult);
}
if (double.TryParse(value, out var doubleResult))
{
return (true, doubleResult);
}
if (Guid.TryParseExact(value, "D", out var guidResult))
{
return (true, guidResult);
}
if (TimeSpan.TryParse(value, out var timeSpanResult))
{
return (true, timeSpanResult);
}
if (DateTime.TryParse(value, out var dateTimeResult))
{
return (true, dateTimeResult);
}
if ((value.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) ||
value.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase) ||
value.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) &&
Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var uriResult))
{
return (true, uriResult);
}
return (false, value);
}
}
@@ -0,0 +1,208 @@
// Copyright © WireMock.Net
using System.Collections;
using System.Text.Json;
using System.Text.Json.Nodes;
using HandlebarsDotNet.Helpers.Models;
using JetBrains.Annotations;
using JsonConverter.System.Text.Json;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Transformers;
/// <summary>
/// JSON body transformer implementation based on System.Text.Json.
/// </summary>
[PublicAPI]
public class SystemTextJsonBodyTransformer(WireMockServerSettings settings) : IJsonBodyTransformer
{
private static readonly SystemTextJsonConverter _jsonConverter = new();
/// <inheritdoc />
public BodyData TransformBodyAsJson(
ITransformerContext transformerContext,
ReplaceNodeOptions options,
object model,
IBodyData original)
{
JsonNode? jsonNode = null;
switch (original.BodyAsJson)
{
case JsonObject bodyAsJsonObject:
jsonNode = CloneNode(bodyAsJsonObject);
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
break;
case JsonArray bodyAsJsonArray:
jsonNode = CloneNode(bodyAsJsonArray);
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
break;
case var bodyAsEnumerable when bodyAsEnumerable is IEnumerable and not string:
jsonNode = JsonSerializer.SerializeToNode(bodyAsEnumerable);
if (jsonNode != null)
{
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
}
break;
case string bodyAsString:
jsonNode = ReplaceSingleNode(transformerContext, options, bodyAsString, model);
break;
case not null:
jsonNode = JsonSerializer.SerializeToNode(original.BodyAsJson);
if (jsonNode != null)
{
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
}
break;
}
return new BodyData
{
Encoding = original.Encoding,
DetectedBodyType = original.DetectedBodyType,
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
ProtoDefinition = original.ProtoDefinition,
ProtoBufMessageType = original.ProtoBufMessageType,
BodyAsJson = jsonNode
};
}
private JsonNode ParseAsJsonObject(string stringValue)
{
if (_jsonConverter.IsValidJson(stringValue))
{
try
{
var parsed = JsonNode.Parse(stringValue);
if (parsed is JsonObject)
{
return parsed;
}
}
catch
{
settings.Logger.Warn("Failed to parse string ''{0}'' as JSON. Returning the original string value.", stringValue);
}
}
return JsonValue.Create(stringValue)!;
}
private JsonNode? ReplaceSingleNode(ITransformerContext transformerContext, ReplaceNodeOptions options, string stringValue, object model)
{
var transformedString = transformerContext.ParseAndRender(stringValue, model);
if (!string.Equals(stringValue, transformedString))
{
return ReplaceNodeValue(options, transformedString);
}
return JsonValue.Create(stringValue);
}
private JsonNode? WalkNode(ITransformerContext transformerContext, ReplaceNodeOptions options, JsonNode? node, object model)
{
switch (node)
{
case JsonObject jsonObject:
foreach (var property in jsonObject.ToArray())
{
jsonObject[property.Key] = WalkNode(transformerContext, options, property.Value, model);
}
return jsonObject;
case JsonArray jsonArray:
for (var i = 0; i < jsonArray.Count; i++)
{
jsonArray[i] = WalkNode(transformerContext, options, jsonArray[i], model);
}
return jsonArray;
case JsonValue jsonValue when jsonValue.TryGetValue<string>(out var stringValue):
if (string.IsNullOrEmpty(stringValue))
{
return jsonValue;
}
var transformed = transformerContext.ParseAndEvaluate(stringValue!, model);
return !Equals(stringValue, transformed) ? ReplaceNodeValue(options, transformed) ?? jsonValue : jsonValue;
default:
return node;
}
}
private JsonNode? ReplaceNodeValue(ReplaceNodeOptions options, object? transformedValue)
{
switch (transformedValue)
{
case JsonNode jsonNode:
return CloneNode(jsonNode);
case string transformedString:
var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString);
return isConvertedFromString
? JsonSerializer.SerializeToNode(convertedValueFromString)
: ParseAsJsonObject(transformedString);
case WireMockList<string> strings:
switch (strings.Count)
{
case 1:
return ParseAsJsonObject(strings[0]);
case > 1:
return JsonSerializer.SerializeToNode(strings.ToArray());
}
break;
case { }:
var (isConverted, convertedValue) = TryConvert(options, transformedValue);
if (isConverted)
{
return JsonSerializer.SerializeToNode(convertedValue);
}
break;
}
return null;
}
private static JsonNode? CloneNode(JsonNode? node)
{
#if NET8_0_OR_GREATER
return node?.DeepClone();
#else
return node == null ? null : JsonNode.Parse(node.ToJsonString());
#endif
}
private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value)
{
var valueAsString = value as string;
if (options == ReplaceNodeOptions.Evaluate)
{
if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded))
{
return (true, decoded);
}
return (false, value);
}
if (valueAsString != null)
{
return WrappedString.TryDecode(valueAsString, out var decoded)
? (true, decoded)
: NewtonsoftJsonBodyTransformer.TryConvertToKnownType(valueAsString);
}
return (false, value);
}
}
+8 -9
View File
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using Stef.Validation;
@@ -129,11 +128,11 @@ internal static class BodyParser
{
Guard.NotNull(settings);
var bodyWithContentEncoding = await ReadBytesAsync(settings.Stream, settings.ContentEncoding, settings.DecompressGZipAndDeflate).ConfigureAwait(false);
var (ContentType, Bytes) = await ReadBytesAsync(settings.Stream, settings.ContentEncoding, settings.DecompressGZipAndDeflate).ConfigureAwait(false);
var data = new BodyData
{
BodyAsBytes = bodyWithContentEncoding.Bytes,
DetectedCompression = bodyWithContentEncoding.ContentType,
BodyAsBytes = Bytes,
DetectedCompression = ContentType,
DetectedBodyType = BodyType.Bytes,
DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(settings.ContentType)
};
@@ -169,17 +168,17 @@ internal static class BodyParser
data.DetectedBodyType = BodyType.FormUrlEncoded;
}
// If string is not null or empty, try to deserialize the string to a JObject
if (settings.DeserializeJson && JsonUtils.IsJson(data.BodyAsString))
// If string is not null or empty, try to deserialize the string
if (settings.DeserializeJson && settings.DefaultJsonConverter.IsValidJson(data.BodyAsString))
{
try
{
data.BodyAsJson = JsonUtils.DeserializeObject(data.BodyAsString);
data.BodyAsJson = settings.DefaultJsonConverter.Deserialize<object>(data.BodyAsString);
data.DetectedBodyType = BodyType.Json;
}
catch
{
// JsonConvert failed, just ignore.
// JsonConverter failed, just ignore.
}
}
}
@@ -202,7 +201,7 @@ internal static class BodyParser
return (null, data);
}
public static bool IsProbablyText(byte[] data)
private static bool IsProbablyText(byte[] data)
{
if (data.Length == 0)
{
@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using System.IO;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
namespace WireMock.Util;
@@ -35,4 +36,13 @@ internal class BodyParserSettings
/// Try to deserialize the body as FormUrlEncoded.
/// </summary>
public bool DeserializeFormUrlEncoded { get; set; } = true;
/// <summary>
/// Gets or sets the default JSON converter used for deserialization.
/// </summary>
/// <remarks>
/// Set this property to customize how objects are serialized to and deserialized from JSON during mapping.
/// Default is <see cref="NewtonsoftJsonConverter"/>.
/// </remarks>
public IJsonConverter DefaultJsonConverter { get; set; } = new NewtonsoftJsonConverter();
}
-137
View File
@@ -1,137 +0,0 @@
// Copyright © WireMock.Net
using System;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Serialization;
namespace WireMock.Util;
internal static class JsonUtils
{
public static bool IsJson(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
value = value!.Trim();
return (value.StartsWith("{") && value.EndsWith("}")) || (value.StartsWith("[") && value.EndsWith("]"));
}
public 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 JToken
value = JObject.Parse(strInput!);
return true;
}
catch
{
return false;
}
}
public static string Serialize(object value)
{
return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues);
}
public static byte[] SerializeAsPactFile(object value)
{
var json = JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsPact);
return Encoding.UTF8.GetBytes(json);
}
/// <summary>
/// Load a Newtonsoft.Json.Linq.JObject from a string that contains JSON.
/// Using : DateParseHandling = DateParseHandling.None
/// </summary>
/// <param name="json">A System.String that contains JSON.</param>
/// <returns>A Newtonsoft.Json.Linq.JToken populated from the string that contains JSON.</returns>
public static JToken Parse(string json)
{
return JsonConvert.DeserializeObject<JToken>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!;
}
/// <summary>
/// Deserializes the JSON to a .NET object.
/// Using : DateParseHandling = DateParseHandling.None
/// </summary>
/// <param name="json">A System.String that contains JSON.</param>
/// <returns>The deserialized object from the JSON string.</returns>
public static object DeserializeObject(string json)
{
return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!;
}
/// <summary>
/// Deserializes the JSON to the specified .NET type.
/// Using : DateParseHandling = DateParseHandling.None
/// </summary>
/// <param name="json">A System.String that contains JSON.</param>
/// <returns>The deserialized object from the JSON string.</returns>
public static T DeserializeObject<T>(string json)
{
return JsonConvert.DeserializeObject<T>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!;
}
public static T? TryDeserializeObject<T>(string json)
{
try
{
return JsonConvert.DeserializeObject<T>(json);
}
catch
{
return default;
}
}
public static T ParseJTokenToObject<T>(object? value)
{
if (value != null && value.GetType() == typeof(T))
{
return (T)value;
}
return value switch
{
JToken tokenValue => tokenValue.ToObject<T>()!,
_ => throw new NotSupportedException($"Unable to convert value to {typeof(T)}.")
};
}
public static JToken ConvertValueToJToken(object value)
{
// Check if JToken, string, IEnumerable or object
switch (value)
{
case JToken tokenValue:
return tokenValue;
case string stringValue:
return Parse(stringValue);
case IEnumerable enumerableValue:
return JArray.FromObject(enumerableValue);
default:
return JObject.FromObject(value);
}
}
}
@@ -3,7 +3,6 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Stef.Validation;
@@ -30,7 +30,8 @@
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="AnyOf" Version="0.5.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.9.0" />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.12.0" />
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.12.0" />
</ItemGroup>
<ItemGroup>