From f6452d1dce9bbdde52a4c68df8fd484027971b7a Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 16 Dec 2025 22:37:26 +0100 Subject: [PATCH 1/4] MappingSerializer --- .../Serialization/MappingSerializer.cs | 49 +++ .../WireMock.Net.Minimal.csproj | 2 + .../Serialization/MappingSerializerTests.cs | 338 ++++++++++++++++++ 3 files changed, 389 insertions(+) create mode 100644 src/WireMock.Net.Minimal/Serialization/MappingSerializer.cs create mode 100644 test/WireMock.Net.Tests/Serialization/MappingSerializerTests.cs diff --git a/src/WireMock.Net.Minimal/Serialization/MappingSerializer.cs b/src/WireMock.Net.Minimal/Serialization/MappingSerializer.cs new file mode 100644 index 000000000..577caffaa --- /dev/null +++ b/src/WireMock.Net.Minimal/Serialization/MappingSerializer.cs @@ -0,0 +1,49 @@ +// Copyright © WireMock.Net + +using System; +using JsonConverter.Abstractions; +using Newtonsoft.Json.Linq; +#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461 +using System.Text.Json; +#endif + +namespace WireMock.Serialization; + +internal class MappingSerializer(IJsonConverter jsonConverter) +{ + internal T[] DeserializeJsonToArray(string value) + { + return DeserializeObjectToArray(jsonConverter.Deserialize(value)!); + } + + internal static T[] DeserializeObjectToArray(object value) + { + if (value is JArray jArray) + { + return jArray.ToObject()!; + } + + if (value is JObject jObject) + { + var singleResult = jObject.ToObject(); + 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()!; + } + + if (jElement.ValueKind == JsonValueKind.Object) + { + var singleResult = jElement.Deserialize(); + return [singleResult!]; + } + } +#endif + throw new InvalidOperationException("Cannot deserialize the provided value to an array or object."); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj index 6d245c539..0708611b8 100644 --- a/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj +++ b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj @@ -118,11 +118,13 @@ + + diff --git a/test/WireMock.Net.Tests/Serialization/MappingSerializerTests.cs b/test/WireMock.Net.Tests/Serialization/MappingSerializerTests.cs new file mode 100644 index 000000000..b50ee74e8 --- /dev/null +++ b/test/WireMock.Net.Tests/Serialization/MappingSerializerTests.cs @@ -0,0 +1,338 @@ +// Copyright © WireMock.Net + +using System; +using FluentAssertions; +using JsonConverter.Newtonsoft.Json; +using WireMock.Admin.Mappings; +using WireMock.Serialization; +using Xunit; +#if NET8_0_OR_GREATER +using JsonConverter.System.Text.Json; +#endif + +namespace WireMock.Net.Tests.Serialization; + +public class MappingSerializerTests +{ + private const string SingleMappingJson = + """ + { + "Guid": "12345678-1234-1234-1234-123456789012", + "Priority": 1, + "Request": { + "Path": "/test" + }, + "Response": { + "StatusCode": 200 + } + } + """; + + private const string ArrayMappingJson = + """ + [ + { + "Guid": "12345678-1234-1234-1234-123456789012", + "Priority": 1, + "Request": { + "Path": "/test1" + }, + "Response": { + "StatusCode": 200 + } + }, + { + "Guid": "87654321-4321-4321-4321-210987654321", + "Priority": 2, + "Request": { + "Path": "/test2" + }, + "Response": { + "StatusCode": 404 + } + } + ] + """; + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_SingleObject_ShouldReturnArray() + { + // Arrange + var jsonConverter = new NewtonsoftJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + + // Act + var result = serializer.DeserializeJsonToArray(SingleMappingJson); + + // Assert + result.Should().NotBeNull(); + result.Should().HaveCount(1); + result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012")); + result[0].Priority.Should().Be(1); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_Array_ShouldReturnArray() + { + // Arrange + var jsonConverter = new NewtonsoftJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + + // Act + var result = serializer.DeserializeJsonToArray(ArrayMappingJson); + + // Assert + result.Should().NotBeNull(); + result.Should().HaveCount(2); + result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012")); + result[0].Priority.Should().Be(1); + result[1].Guid.Should().Be(Guid.Parse("87654321-4321-4321-4321-210987654321")); + result[1].Priority.Should().Be(2); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_EmptyArray_ShouldReturnEmptyArray() + { + // Arrange + var jsonConverter = new NewtonsoftJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var emptyArrayJson = "[]"; + + // Act + var result = serializer.DeserializeJsonToArray(emptyArrayJson); + + // Assert + result.Should().NotBeNull(); + result.Should().BeEmpty(); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_InvalidJson_ShouldThrowException() + { + // Arrange + var jsonConverter = new NewtonsoftJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var invalidJson = "not valid json"; + + // Act + Action act = () => serializer.DeserializeJsonToArray(invalidJson); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_ComplexMapping_ShouldDeserializeCorrectly() + { + // Arrange + var jsonConverter = new NewtonsoftJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var complexJson = + """ + { + "Guid": "12345678-1234-1234-1234-123456789012", + "Title": "Test Mapping", + "Description": "A test mapping", + "Priority": 10, + "Scenario": "TestScenario", + "WhenStateIs": "Started", + "SetStateTo": "Finished", + "Request": { + "Path": "/api/test", + "Methods": ["GET", "POST"] + }, + "Response": { + "StatusCode": 201, + "Body": "Test Response" + } + } + """; + + // Act + var result = serializer.DeserializeJsonToArray(complexJson); + + // Assert + result.Should().NotBeNull(); + result.Should().HaveCount(1); + result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012")); + result[0].Title.Should().Be("Test Mapping"); + result[0].Description.Should().Be("A test mapping"); + result[0].Priority.Should().Be(10); + result[0].Scenario.Should().Be("TestScenario"); + result[0].WhenStateIs.Should().Be("Started"); + result[0].SetStateTo.Should().Be("Finished"); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_NullValue_ShouldThrowException() + { + // Arrange + var jsonConverter = new NewtonsoftJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var nullJson = "null"; + + // Act + Action act = () => serializer.DeserializeJsonToArray(nullJson); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_PrimitiveValue_ShouldThrowInvalidOperationException() + { + // Arrange + var jsonConverter = new NewtonsoftJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var primitiveJson = "\"string value\""; + + // Act + Action act = () => serializer.DeserializeJsonToArray(primitiveJson); + + // Assert + act.Should().Throw() + .WithMessage("Cannot deserialize the provided value to an array or object."); + } + +#if NET8_0_OR_GREATER + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_SingleObject_ShouldReturnArray() + { + // Arrange + var jsonConverter = new SystemTextJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + + // Act + var result = serializer.DeserializeJsonToArray(SingleMappingJson); + + // Assert + result.Should().NotBeNull(); + result.Should().HaveCount(1); + result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012")); + result[0].Priority.Should().Be(1); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_Array_ShouldReturnArray() + { + // Arrange + var jsonConverter = new SystemTextJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + + // Act + var result = serializer.DeserializeJsonToArray(ArrayMappingJson); + + // Assert + result.Should().NotBeNull(); + result.Should().HaveCount(2); + result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012")); + result[0].Priority.Should().Be(1); + result[1].Guid.Should().Be(Guid.Parse("87654321-4321-4321-4321-210987654321")); + result[1].Priority.Should().Be(2); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_EmptyArray_ShouldReturnEmptyArray() + { + // Arrange + var jsonConverter = new SystemTextJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var emptyArrayJson = "[]"; + + // Act + var result = serializer.DeserializeJsonToArray(emptyArrayJson); + + // Assert + result.Should().NotBeNull(); + result.Should().BeEmpty(); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_InvalidJson_ShouldThrowException() + { + // Arrange + var jsonConverter = new SystemTextJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var invalidJson = "not valid json"; + + // Act + Action act = () => serializer.DeserializeJsonToArray(invalidJson); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_ComplexMapping_ShouldDeserializeCorrectly() + { + // Arrange + var jsonConverter = new SystemTextJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var complexJson = + """ + { + "Guid": "12345678-1234-1234-1234-123456789012", + "Title": "Test Mapping", + "Description": "A test mapping", + "Priority": 10, + "Scenario": "TestScenario", + "WhenStateIs": "Started", + "SetStateTo": "Finished", + "Request": { + "Path": "/api/test", + "Methods": ["GET", "POST"] + }, + "Response": { + "StatusCode": 201, + "Body": "Test Response" + } + } + """; + + // Act + var result = serializer.DeserializeJsonToArray(complexJson); + + // Assert + result.Should().NotBeNull(); + result.Should().HaveCount(1); + result[0].Guid.Should().Be(Guid.Parse("12345678-1234-1234-1234-123456789012")); + result[0].Title.Should().Be("Test Mapping"); + result[0].Description.Should().Be("A test mapping"); + result[0].Priority.Should().Be(10); + result[0].Scenario.Should().Be("TestScenario"); + result[0].WhenStateIs.Should().Be("Started"); + result[0].SetStateTo.Should().Be("Finished"); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_NullValue_ShouldThrowException() + { + // Arrange + var jsonConverter = new SystemTextJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var nullJson = "null"; + + // Act + Action act = () => serializer.DeserializeJsonToArray(nullJson); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_PrimitiveValue_ShouldThrowInvalidOperationException() + { + // Arrange + var jsonConverter = new SystemTextJsonConverter(); + var serializer = new MappingSerializer(jsonConverter); + var primitiveJson = "\"string value\""; + + // Act + Action act = () => serializer.DeserializeJsonToArray(primitiveJson); + + // Assert + act.Should().Throw() + .WithMessage("Cannot deserialize the provided value to an array or object."); + } +#endif +} \ No newline at end of file From de15e0d48eb9278e0b8112ba23c055e8f60a8ab8 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 16 Dec 2025 23:36:19 +0100 Subject: [PATCH 2/4] json --- .../Server/WireMockServer.Admin.cs | 42 ++++++------------- .../WireMockServer.ImportWireMockOrg.cs | 2 +- .../Server/WireMockServer.cs | 7 +++- .../WireMock.Net.Minimal.csproj | 4 +- .../Settings/WireMockServerSettings.cs | 11 +++++ 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs index 5c248a26c..96ce9ad14 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs @@ -236,7 +236,7 @@ public bool ReadStaticMappingAndAddOrUpdate(string path) if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value)) { - var mappingModels = DeserializeJsonToArray(value); + var mappingModels = _mappingSerializer.DeserializeJsonToArray(value); if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename)) { ConvertMappingAndRegisterAsRespondProvider(mappingModels[0], guidFromFilename, path); @@ -859,6 +859,18 @@ private static ResponseMessage ToResponseMessage(string text) }; } + private static T[] DeserializeRequestMessageToArray(IRequestMessage requestMessage) + { + if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null) + { + var bodyAsJson = requestMessage.BodyData.BodyAsJson!; + + return MappingSerializer.DeserializeObjectToArray(bodyAsJson); + } + + throw new NotSupportedException(); + } + private static T DeserializeObject(IRequestMessage requestMessage) where T : new() { switch (requestMessage.BodyData?.DetectedBodyType) @@ -874,32 +886,4 @@ private static ResponseMessage ToResponseMessage(string text) throw new NotSupportedException(); } } - - private static T[] DeserializeRequestMessageToArray(IRequestMessage requestMessage) - { - if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null) - { - var bodyAsJson = requestMessage.BodyData.BodyAsJson; - - return DeserializeObjectToArray(bodyAsJson); - } - - throw new NotSupportedException(); - } - - private static T[] DeserializeJsonToArray(string value) - { - return DeserializeObjectToArray(JsonUtils.DeserializeObject(value)); - } - - private static T[] DeserializeObjectToArray(object value) - { - if (value is JArray jArray) - { - return jArray.ToObject()!; - } - - var singleResult = ((JObject)value).ToObject(); - return new[] { singleResult! }; - } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.ImportWireMockOrg.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.ImportWireMockOrg.cs index 1cc29b61e..10c6751d4 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.ImportWireMockOrg.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.ImportWireMockOrg.cs @@ -31,7 +31,7 @@ public void ReadStaticWireMockOrgMappingAndAddOrUpdate(string path) if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value)) { - var mappings = DeserializeJsonToArray(value); + var mappings = _mappingSerializer.DeserializeJsonToArray(value); foreach (var mapping in mappings) { if (mappings.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename)) diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.cs index b7d46738d..71a8efb76 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.cs @@ -8,9 +8,11 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Runtime; using System.Threading; using AnyOfTypes; using JetBrains.Annotations; +using JsonConverter.Newtonsoft.Json; using Newtonsoft.Json; using Stef.Validation; using WireMock.Admin.Mappings; @@ -47,6 +49,7 @@ public partial class WireMockServer : IWireMockServer private readonly MappingBuilder _mappingBuilder; private readonly IGuidUtils _guidUtils = new GuidUtils(); private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils(); + private readonly MappingSerializer _mappingSerializer; /// [PublicAPI] @@ -357,6 +360,8 @@ protected WireMockServer(WireMockServerSettings settings) { _settings = Guard.NotNull(settings); + _mappingSerializer = new MappingSerializer(settings.MappingJsonSerializer ?? new NewtonsoftJsonConverter()); + // Set default values if not provided _settings.Logger = settings.Logger ?? new WireMockNullLogger(); _settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler(); @@ -639,7 +644,7 @@ public IWireMockServer WithMapping(params MappingModel[] mappings) [PublicAPI] public IWireMockServer WithMapping(string mappings) { - var mappingModels = DeserializeJsonToArray(mappings); + var mappingModels = _mappingSerializer.DeserializeJsonToArray(mappings); foreach (var mappingModel in mappingModels) { ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid()); diff --git a/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj index 0708611b8..57b60323c 100644 --- a/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj +++ b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj @@ -57,8 +57,8 @@ - - + + diff --git a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs index 86f99f382..bf1cbd8a8 100644 --- a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs @@ -14,6 +14,7 @@ using WireMock.Types; using System.Globalization; using WireMock.Models; +using JsonConverter.Abstractions; #if USE_ASPNETCORE using Microsoft.Extensions.DependencyInjection; @@ -338,4 +339,14 @@ public class WireMockServerSettings /// [PublicAPI] public HandlebarsSettings? HandlebarsSettings { get; set; } + + /// + /// Gets or sets the JSON converter used for MappingModel serialization. + /// + /// + /// Set this property to customize how objects are serialized to and deserialized from JSON during mapping. + /// If not set, the NewtonsoftJsonConverter will be used. + /// + [PublicAPI] + public IJsonConverter? MappingJsonSerializer { get; set; } } \ No newline at end of file From dd4770a78b277df13d4e1463b191a795b6a22723 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 18 Dec 2025 18:15:37 +0100 Subject: [PATCH 3/4] . --- .../Serialization/MappingSerializerTests.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/WireMock.Net.Tests/Serialization/MappingSerializerTests.cs b/test/WireMock.Net.Tests/Serialization/MappingSerializerTests.cs index b50ee74e8..f1f7a11db 100644 --- a/test/WireMock.Net.Tests/Serialization/MappingSerializerTests.cs +++ b/test/WireMock.Net.Tests/Serialization/MappingSerializerTests.cs @@ -134,9 +134,6 @@ public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_ComplexM "Title": "Test Mapping", "Description": "A test mapping", "Priority": 10, - "Scenario": "TestScenario", - "WhenStateIs": "Started", - "SetStateTo": "Finished", "Request": { "Path": "/api/test", "Methods": ["GET", "POST"] @@ -158,9 +155,6 @@ public void MappingSerializer_DeserializeJsonToArray_WithNewtonsoftJson_ComplexM result[0].Title.Should().Be("Test Mapping"); result[0].Description.Should().Be("A test mapping"); result[0].Priority.Should().Be(10); - result[0].Scenario.Should().Be("TestScenario"); - result[0].WhenStateIs.Should().Be("Started"); - result[0].SetStateTo.Should().Be("Finished"); } [Fact] @@ -275,9 +269,6 @@ public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_ComplexM "Title": "Test Mapping", "Description": "A test mapping", "Priority": 10, - "Scenario": "TestScenario", - "WhenStateIs": "Started", - "SetStateTo": "Finished", "Request": { "Path": "/api/test", "Methods": ["GET", "POST"] @@ -299,9 +290,6 @@ public void MappingSerializer_DeserializeJsonToArray_WithSystemTextJson_ComplexM result[0].Title.Should().Be("Test Mapping"); result[0].Description.Should().Be("A test mapping"); result[0].Priority.Should().Be(10); - result[0].Scenario.Should().Be("TestScenario"); - result[0].WhenStateIs.Should().Be("Started"); - result[0].SetStateTo.Should().Be("Finished"); } [Fact] From 708af550515bebe17f3760533fd3cf5465dbe96d Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 20 Dec 2025 10:32:26 +0100 Subject: [PATCH 4/4] 0.8.0 --- .../Admin/Mappings/ResponseModel.cs | 2 +- .../Models/IBodyData.cs | 2 +- .../WireMock.Net.Extensions.Routing.csproj | 4 +-- src/WireMock.Net.Minimal/MappingBuilder.cs | 5 ++- .../Serialization/MappingToFileSaver.cs | 10 ++++-- .../Server/WireMockServer.Admin.cs | 20 +++++++----- .../Server/WireMockServer.cs | 3 +- .../WireMock.Net.Minimal.csproj | 10 +++--- .../WireMock.Net.RestClient.csproj | 2 +- .../JsonSerializationConstants.cs | 19 ++++++++---- .../Settings/WireMockServerSettings.cs | 7 +++-- .../WireMock.Net.Shared.csproj | 7 +++-- .../ResponseBuilders/ResponseWithBodyTests.cs | 31 ++++++++++++++++--- .../WireMock.Net.Tests.csproj | 4 +-- 14 files changed, 83 insertions(+), 43 deletions(-) diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs index 3570439e1..7f585c329 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs @@ -32,7 +32,7 @@ public class ResponseModel public object? BodyAsJson { get; set; } /// - /// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. + /// Gets or sets a value indicating whether the Json Body String needs to be indented. /// public bool? BodyAsJsonIndented { get; set; } diff --git a/src/WireMock.Net.Abstractions/Models/IBodyData.cs b/src/WireMock.Net.Abstractions/Models/IBodyData.cs index 79b737d5f..593d4c406 100644 --- a/src/WireMock.Net.Abstractions/Models/IBodyData.cs +++ b/src/WireMock.Net.Abstractions/Models/IBodyData.cs @@ -37,7 +37,7 @@ public interface IBodyData object? BodyAsJson { get; set; } /// - /// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. + /// Gets or sets a value indicating whether the Json Body String needs to be indented. /// bool? BodyAsJsonIndented { get; set; } diff --git a/src/WireMock.Net.Extensions.Routing/WireMock.Net.Extensions.Routing.csproj b/src/WireMock.Net.Extensions.Routing/WireMock.Net.Extensions.Routing.csproj index 707912275..533c93a7c 100644 --- a/src/WireMock.Net.Extensions.Routing/WireMock.Net.Extensions.Routing.csproj +++ b/src/WireMock.Net.Extensions.Routing/WireMock.Net.Extensions.Routing.csproj @@ -1,4 +1,4 @@ - + WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET Gennadii Saltyshchak @@ -25,7 +25,7 @@ - + diff --git a/src/WireMock.Net.Minimal/MappingBuilder.cs b/src/WireMock.Net.Minimal/MappingBuilder.cs index 6e900e1a1..79b85ef67 100644 --- a/src/WireMock.Net.Minimal/MappingBuilder.cs +++ b/src/WireMock.Net.Minimal/MappingBuilder.cs @@ -3,7 +3,6 @@ using System; using System.Linq; using System.Text; -using Newtonsoft.Json; using Stef.Validation; using WireMock.Admin.Mappings; using WireMock.Matchers.Request; @@ -164,8 +163,8 @@ private void RegisterMapping(IMapping mapping, bool saveToFile) } } - private static string ToJson(object value) + private string ToJson(object value) { - return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsDefault); + return _settings.DefaultJsonSerializer.Serialize(value, JsonSerializationConstants.JsonConverterOptionsDefault); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Serialization/MappingToFileSaver.cs b/src/WireMock.Net.Minimal/Serialization/MappingToFileSaver.cs index 3e6c8610c..cd76888bb 100644 --- a/src/WireMock.Net.Minimal/Serialization/MappingToFileSaver.cs +++ b/src/WireMock.Net.Minimal/Serialization/MappingToFileSaver.cs @@ -2,7 +2,8 @@ using System.IO; using System.Linq; -using Newtonsoft.Json; +using JsonConverter.Abstractions; +using JsonConverter.Newtonsoft.Json; using Stef.Validation; using WireMock.Settings; @@ -12,12 +13,15 @@ internal class MappingToFileSaver { private readonly WireMockServerSettings _settings; private readonly MappingConverter _mappingConverter; + private readonly IJsonConverter _jsonConverter; private readonly MappingFileNameSanitizer _fileNameSanitizer; public MappingToFileSaver(WireMockServerSettings settings, MappingConverter mappingConverter) { _settings = Guard.NotNull(settings); _mappingConverter = Guard.NotNull(mappingConverter); + + _jsonConverter = settings.DefaultJsonSerializer ?? new NewtonsoftJsonConverter(); _fileNameSanitizer = new MappingFileNameSanitizer(settings); } @@ -56,6 +60,8 @@ private void Save(object value, string path) { _settings.Logger.Info("Saving Mapping file {0}", path); - _settings.FileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsDefault)); + var json = _jsonConverter.Serialize(value, JsonSerializationConstants.JsonConverterOptionsDefault); + + _settings.FileSystemHandler.WriteMappingFile(path, json); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs index 96ce9ad14..45dbc141a 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs @@ -7,7 +7,7 @@ using System.Net; using System.Text; using JetBrains.Annotations; -using Newtonsoft.Json; +using JsonConverter.Abstractions; using Newtonsoft.Json.Linq; using Stef.Validation; using WireMock.Admin.Mappings; @@ -826,25 +826,31 @@ private void EnhancedFileSystemWatcherDeleted(object sender, FileSystemEventArgs } } - private static Encoding? ToEncoding(EncodingModel? encodingModel) + private ResponseMessage ToJson(T result, bool keepNullValues = false, object? statusCode = null) { - return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; - } + var jsonOptions = new JsonConverterOptions + { + WriteIndented = true, + IgnoreNullValues = !keepNullValues + }; - private static ResponseMessage ToJson(T result, bool keepNullValues = false, object? statusCode = null) - { return new ResponseMessage { BodyData = new BodyData { DetectedBodyType = BodyType.String, - BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault) + BodyAsString = _settings.DefaultJsonSerializer.Serialize(result!, jsonOptions) }, StatusCode = statusCode ?? (int)HttpStatusCode.OK, Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(WireMockConstants.ContentTypeJson) } } }; } + private static Encoding? ToEncoding(EncodingModel? encodingModel) + { + return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; + } + private static ResponseMessage ToResponseMessage(string text) { return new ResponseMessage diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.cs index 71a8efb76..99a1df308 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Runtime; using System.Threading; using AnyOfTypes; using JetBrains.Annotations; @@ -360,7 +359,7 @@ protected WireMockServer(WireMockServerSettings settings) { _settings = Guard.NotNull(settings); - _mappingSerializer = new MappingSerializer(settings.MappingJsonSerializer ?? new NewtonsoftJsonConverter()); + _mappingSerializer = new MappingSerializer(settings.DefaultJsonSerializer ?? new NewtonsoftJsonConverter()); // Set default values if not provided _settings.Logger = settings.Logger ?? new WireMockNullLogger(); diff --git a/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj index 57b60323c..f7c3d2dad 100644 --- a/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj +++ b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj @@ -1,4 +1,4 @@ - + Minimal version from the lightweight Http Mocking Server for .NET WireMock.Net.Minimal @@ -57,8 +57,8 @@ - - + + @@ -118,13 +118,13 @@ - + - + diff --git a/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj b/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj index 3baecc439..bff6b9b65 100644 --- a/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj +++ b/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/WireMock.Net.Shared/Serialization/JsonSerializationConstants.cs b/src/WireMock.Net.Shared/Serialization/JsonSerializationConstants.cs index 1fd258a5f..f1b31ef32 100644 --- a/src/WireMock.Net.Shared/Serialization/JsonSerializationConstants.cs +++ b/src/WireMock.Net.Shared/Serialization/JsonSerializationConstants.cs @@ -1,5 +1,6 @@ // Copyright © WireMock.Net +using JsonConverter.Abstractions; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -7,24 +8,30 @@ namespace WireMock.Serialization; internal static class JsonSerializationConstants { - public static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new() + internal static readonly JsonConverterOptions JsonConverterOptionsDefault = new() { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Ignore + WriteIndented = true, + IgnoreNullValues = true }; - public static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new() + //internal static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new() + //{ + // Formatting = Formatting.Indented, + // NullValueHandling = NullValueHandling.Ignore + //}; + + internal static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new() { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Include }; - public static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new() + internal static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new() { DateParseHandling = DateParseHandling.None }; - public static readonly JsonSerializerSettings JsonSerializerSettingsPact = new() + internal static readonly JsonSerializerSettings JsonSerializerSettingsPact = new() { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore, diff --git a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs index bf1cbd8a8..8e63fd9bd 100644 --- a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs @@ -15,6 +15,7 @@ using System.Globalization; using WireMock.Models; using JsonConverter.Abstractions; +using JsonConverter.Newtonsoft.Json; #if USE_ASPNETCORE using Microsoft.Extensions.DependencyInjection; @@ -341,12 +342,12 @@ public class WireMockServerSettings public HandlebarsSettings? HandlebarsSettings { get; set; } /// - /// Gets or sets the JSON converter used for MappingModel serialization. + /// Gets or sets the default JSON converter used for serialization. /// /// /// Set this property to customize how objects are serialized to and deserialized from JSON during mapping. - /// If not set, the NewtonsoftJsonConverter will be used. + /// Default is . /// [PublicAPI] - public IJsonConverter? MappingJsonSerializer { get; set; } + public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter(); } \ No newline at end of file diff --git a/src/WireMock.Net.Shared/WireMock.Net.Shared.csproj b/src/WireMock.Net.Shared/WireMock.Net.Shared.csproj index 2b01dd010..ced033fb4 100644 --- a/src/WireMock.Net.Shared/WireMock.Net.Shared.csproj +++ b/src/WireMock.Net.Shared/WireMock.Net.Shared.csproj @@ -1,4 +1,4 @@ - + Shared interfaces, models, enumerations and types. Stef Heyenrath @@ -48,9 +48,10 @@ - + - + + diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyTests.cs index 253d1ab82..151de0f7a 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyTests.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using FluentAssertions; +using JsonConverter.Newtonsoft.Json; using Moq; using Newtonsoft.Json.Linq; using NFluent; @@ -316,12 +317,11 @@ public async Task Response_ProvideResponse_WithResponseDeleted() var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request1, _settings).ConfigureAwait(false); Check.That(response.Message.StatusCode).IsEqualTo(200); - Check.That(response.Message.BodyData.BodyAsString).Contains("File deleted."); + Check.That(response.Message.BodyData?.BodyAsString).Contains("File deleted."); } -#if !(NET451 || NET452) [Fact] - public async Task Response_ProvideResponse_WithBody_IJsonConverter_SystemTextJson() + public async Task Response_ProvideResponse_WithBody_NewtonsoftJsonConverter() { // Arrange var requestBody = new BodyData @@ -331,13 +331,34 @@ public async Task Response_ProvideResponse_WithBody_IJsonConverter_SystemTextJso }; var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, requestBody); - var responseBuilder = Response.Create().WithBody(new { foo = "bar", n = 42 }, new JsonConverter.System.Text.Json.SystemTextJsonConverter()); + var responseBuilder = Response.Create().WithBody(new { foo = "< > & ' 😀 👍 ❤️", n = 42 }, new NewtonsoftJsonConverter()); // Act var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings).ConfigureAwait(false); // Assert - response.Message.BodyData!.BodyAsString.Should().Be(@"{""foo"":""bar"",""n"":42}"); + response.Message.BodyData!.BodyAsString.Should().Be("""{"foo":"< > & ' 😀 👍 ❤️","n":42}"""); + } + +#if !(NET451 || NET452 || NET461) + [Fact] + public async Task Response_ProvideResponse_WithBody_SystemTextJsonConverter() + { + // Arrange + var requestBody = new BodyData + { + DetectedBodyType = BodyType.String, + BodyAsString = "abc" + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "POST", ClientIp, requestBody); + + var responseBuilder = Response.Create().WithBody(new { foo = "< > & ' 😀 👍 ❤️", n = 42 }, new JsonConverter.System.Text.Json.SystemTextJsonConverter()); + + // Act + var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings).ConfigureAwait(false); + + // Assert + response.Message.BodyData!.BodyAsString.Should().Be("""{"foo":"\u003C \u003E \u0026 \u0027 \uD83D\uDE00 \uD83D\uDC4D \u2764\uFE0F","n":42}"""); } #endif } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index 3602674f6..f1720cefd 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -1,4 +1,4 @@ - + Stef Heyenrath @@ -103,7 +103,7 @@ - +