diff --git a/Activout.RestClient.Json.Test/SimpleValueObjectTest.cs b/Activout.RestClient.Json.Test/SimpleValueObjectTest.cs index f92f036..0834916 100644 --- a/Activout.RestClient.Json.Test/SimpleValueObjectTest.cs +++ b/Activout.RestClient.Json.Test/SimpleValueObjectTest.cs @@ -37,8 +37,8 @@ public async Task TestSimpleValueObjectSerialization() .Expect(HttpMethod.Post, BaseUri) .WithContent(JsonSerializer.Serialize(new { - fooBar = "foobar", - nullableInteger = 42 + FooBar = "foobar", + NullableInteger = 42 })) .Respond(HttpStatusCode.OK); diff --git a/Activout.RestClient.Json/SystemTextJsonDefaults.cs b/Activout.RestClient.Json/SystemTextJsonDefaults.cs index 0e8eaa7..78c7c27 100644 --- a/Activout.RestClient.Json/SystemTextJsonDefaults.cs +++ b/Activout.RestClient.Json/SystemTextJsonDefaults.cs @@ -27,9 +27,16 @@ public static class SystemTextJsonDefaults /// Gets the default JSON serializer options. /// public static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = null, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + PropertyNameCaseInsensitive = true + }; + + public static readonly JsonSerializerOptions CamelCaseSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, PropertyNameCaseInsensitive = true }; diff --git a/Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs b/Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs index 3dd1a10..6dfd237 100644 --- a/Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs +++ b/Activout.RestClient.Newtonsoft.Json.Test/RestClientTests.cs @@ -191,13 +191,12 @@ public async Task TestQueryParamAsync() public async Task TestPostJsonAsync() { // arrange - var movieId = "FOOBAR"; _mockHttp - .When(HttpMethod.Post, $"{BaseUri}/movies/{movieId}/reviews") + .When(HttpMethod.Post, $"{BaseUri}/movies/FOOBAR/reviews") .WithHeaders("Content-Type", "application/json; charset=utf-8") .Respond(request => { - var content = request.Content.ReadAsStringAsync().Result; + var content = request.Content!.ReadAsStringAsync().Result; content = content.Replace("\"ReviewId\":null", "\"ReviewId\":\"*REVIEW_ID*\""); return new StringContent(content, Encoding.UTF8, "application/json"); }); @@ -205,15 +204,13 @@ public async Task TestPostJsonAsync() var reviewSvc = CreateMovieReviewService(); // act - var text = "This was a delightful comedy, but not terribly realistic."; - var stars = 3; - var review = new Review(stars, text); - var result = await reviewSvc.SubmitReview(movieId, review); + var review = new Review(3, "This was a delightful comedy, but not terribly realistic."); + var result = await reviewSvc.SubmitReview("FOOBAR", review); // assert Assert.Equal("*REVIEW_ID*", result.ReviewId); - Assert.Equal(stars, result.Stars); - Assert.Equal(text, result.Text); + Assert.Equal(3, result.Stars); + Assert.Equal("This was a delightful comedy, but not terribly realistic.", result.Text); } [Fact] diff --git a/Activout.RestClient.Newtonsoft.Json/NewtonsoftJsonDefaults.cs b/Activout.RestClient.Newtonsoft.Json/NewtonsoftJsonDefaults.cs index 9fe62b8..e551508 100644 --- a/Activout.RestClient.Newtonsoft.Json/NewtonsoftJsonDefaults.cs +++ b/Activout.RestClient.Newtonsoft.Json/NewtonsoftJsonDefaults.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; namespace Activout.RestClient.Newtonsoft.Json; @@ -13,8 +14,19 @@ public static class NewtonsoftJsonDefaults public static readonly JsonConverter[] DefaultJsonConverters = [new SimpleValueObjectConverter()]; + public static readonly DefaultContractResolver CamelCasePropertyNamesContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + public static readonly JsonSerializerSettings DefaultJsonSerializerSettings = new() { Converters = DefaultJsonConverters.ToList() }; + + public static readonly JsonSerializerSettings CamelCaseSerializerSettings = new() + { + Converters = DefaultJsonConverters.ToList(), + ContractResolver = CamelCasePropertyNamesContractResolver, + }; } \ No newline at end of file diff --git a/Activout.RestClient.Test.Json/RestClientTests.cs b/Activout.RestClient.Test.Json/RestClientTests.cs index efab20e..9eb15ea 100644 --- a/Activout.RestClient.Test.Json/RestClientTests.cs +++ b/Activout.RestClient.Test.Json/RestClientTests.cs @@ -7,6 +7,8 @@ using RichardSzalay.MockHttp; using System.Net; using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using Xunit; using Xunit.Abstractions; @@ -18,6 +20,12 @@ public enum JsonImplementation NewtonsoftJson } +public enum JsonNullValueHandling +{ + Include, + Ignore +} + public class RestClientTests(ITestOutputHelper outputHelper) { private const string BaseUri = "https://example.com/movieReviewService"; @@ -28,21 +36,55 @@ public class RestClientTests(ITestOutputHelper outputHelper) private readonly MockHttpMessageHandler _mockHttp = new(); private readonly ILoggerFactory _loggerFactory = LoggerFactoryHelpers.CreateLoggerFactory(outputHelper); - private IRestClientBuilder CreateRestClientBuilder(JsonImplementation jsonImplementation) + private static readonly JsonSerializerOptions SystemTextJsonIncludeNulls = + new(SystemTextJsonDefaults.SerializerOptions) + { + DefaultIgnoreCondition = JsonIgnoreCondition.Never + }; + + private static readonly JsonSerializerOptions SystemTextJsonIgnoreNulls = + new(SystemTextJsonDefaults.SerializerOptions) + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + private static readonly JsonSerializerSettings NewtonsoftJsonIncludeNulls = + new(NewtonsoftJsonDefaults.DefaultJsonSerializerSettings) + { + NullValueHandling = NullValueHandling.Include + }; + + private static readonly JsonSerializerSettings NewtonsoftJsonIgnoreNulls = + new(NewtonsoftJsonDefaults.DefaultJsonSerializerSettings) + { + NullValueHandling = NullValueHandling.Ignore + }; + + private IRestClientBuilder CreateRestClientBuilder(JsonImplementation jsonImplementation, + JsonNullValueHandling nullValueHandling = JsonNullValueHandling.Ignore) { var builder = _restClientFactory.CreateBuilder(); - + + var jsonSerializerOptions = nullValueHandling == JsonNullValueHandling.Ignore + ? SystemTextJsonIgnoreNulls + : SystemTextJsonIncludeNulls; + + var jsonSerializerSettings = nullValueHandling == JsonNullValueHandling.Ignore + ? NewtonsoftJsonIgnoreNulls + : NewtonsoftJsonIncludeNulls; + return jsonImplementation switch { - JsonImplementation.SystemTextJson => builder.WithSystemTextJson(), - JsonImplementation.NewtonsoftJson => builder.WithNewtonsoftJson(), + JsonImplementation.SystemTextJson => builder.WithSystemTextJson(jsonSerializerOptions), + JsonImplementation.NewtonsoftJson => builder.WithNewtonsoftJson(jsonSerializerSettings), _ => throw new ArgumentOutOfRangeException(nameof(jsonImplementation)) }; } - private IMovieReviewService CreateMovieReviewService(JsonImplementation jsonImplementation) + private IMovieReviewService CreateMovieReviewService(JsonImplementation jsonImplementation, + JsonNullValueHandling nullValueHandling = JsonNullValueHandling.Ignore) { - return CreateRestClientBuilder(jsonImplementation) + return CreateRestClientBuilder(jsonImplementation, nullValueHandling) .Accept("application/json") .ContentType("application/json") .With(_loggerFactory.CreateLogger()) @@ -112,12 +154,12 @@ private void ExpectGetAllReviewsAndReturnError(string movieId = MovieId) _mockHttp .Expect(HttpMethod.Get, $"{BaseUri}/movies/{movieId}/reviews") .Respond(HttpStatusCode.NotFound, request => new StringContent(JsonConvert.SerializeObject(new - { - Errors = new object[] + { + Errors = new object[] { - new {Message = "Sorry, that page does not exist", Code = 34} + new { Message = "Sorry, that page does not exist", Code = 34 } } - }), + }), Encoding.UTF8, "application/json")); } @@ -131,12 +173,12 @@ public void TestErrorSync(JsonImplementation jsonImplementation) _mockHttp .When(HttpMethod.Get, $"{BaseUri}/movies/{MovieId}/reviews/{ReviewId}") .Respond(HttpStatusCode.NotFound, request => new StringContent(JsonConvert.SerializeObject(new - { - Errors = new object[] + { + Errors = new object[] { - new {Message = "Sorry, that page does not exist", Code = 34} + new { Message = "Sorry, that page does not exist", Code = 34 } } - }), + }), Encoding.UTF8, "application/json")); @@ -205,45 +247,41 @@ public async Task TestQueryParamAsync(JsonImplementation jsonImplementation) } [Theory] - [InlineData(JsonImplementation.SystemTextJson)] - [InlineData(JsonImplementation.NewtonsoftJson)] - public async Task TestPostJsonAsync(JsonImplementation jsonImplementation) + [InlineData(JsonImplementation.SystemTextJson, JsonNullValueHandling.Include)] + [InlineData(JsonImplementation.NewtonsoftJson, JsonNullValueHandling.Include)] + [InlineData(JsonImplementation.SystemTextJson, JsonNullValueHandling.Ignore)] + [InlineData(JsonImplementation.NewtonsoftJson, JsonNullValueHandling.Ignore)] + public async Task TestPostJsonAsync(JsonImplementation jsonImplementation, JsonNullValueHandling nullValueHandling) { // arrange - var movieId = "FOOBAR"; - - // The replacement logic needs to handle different JSON formats _mockHttp - .When(HttpMethod.Post, $"{BaseUri}/movies/{movieId}/reviews") + .When(HttpMethod.Post, $"{BaseUri}/movies/FOOBAR/reviews") .WithHeaders("Content-Type", "application/json; charset=utf-8") .Respond(request => { var content = request.Content!.ReadAsStringAsync().Result; - - // Handle different serialization formats - content = jsonImplementation switch + if (nullValueHandling == JsonNullValueHandling.Include) { - JsonImplementation.SystemTextJson => content.Replace("\"reviewId\":null", "\"reviewId\":\"*REVIEW_ID*\"") - .Replace("}", ",\"reviewId\":\"*REVIEW_ID*\"}"), // Add reviewId if not present - JsonImplementation.NewtonsoftJson => content.Replace("\"ReviewId\":null", "\"ReviewId\":\"*REVIEW_ID*\""), - _ => throw new ArgumentOutOfRangeException(nameof(jsonImplementation)) - }; - + content = content.Replace("\"ReviewId\":null", "\"ReviewId\":\"*REVIEW_ID*\""); + } + else + { + content = content.Replace("}", ",\"ReviewId\":\"*REVIEW_ID*\"}"); + } + return new StringContent(content, Encoding.UTF8, "application/json"); }); - var reviewSvc = CreateMovieReviewService(jsonImplementation); + var reviewSvc = CreateMovieReviewService(jsonImplementation, nullValueHandling); // act - var text = "This was a delightful comedy, but not terribly realistic."; - var stars = 3; - var review = new Review(stars, text); - var result = await reviewSvc.SubmitReview(movieId, review); + var review = new Review(3, "This was a delightful comedy, but not terribly realistic."); + var result = await reviewSvc.SubmitReview("FOOBAR", review); // assert Assert.Equal("*REVIEW_ID*", result.ReviewId); - Assert.Equal(stars, result.Stars); - Assert.Equal(text, result.Text); + Assert.Equal(3, result.Stars); + Assert.Equal("This was a delightful comedy, but not terribly realistic.", result.Text); } [Theory] diff --git a/Activout.RestClient.Test.Json/SimpleValueObjectTest.cs b/Activout.RestClient.Test.Json/SimpleValueObjectTest.cs index 8f51eed..d910054 100644 --- a/Activout.RestClient.Test.Json/SimpleValueObjectTest.cs +++ b/Activout.RestClient.Test.Json/SimpleValueObjectTest.cs @@ -37,24 +37,9 @@ public class SimpleValueObjectTest public async Task TestSimpleValueObjectSerialization(JsonImplementation jsonImplementation) { // Arrange - var expectedContent = jsonImplementation switch - { - JsonImplementation.SystemTextJson => System.Text.Json.JsonSerializer.Serialize(new - { - fooBar = "foobar", - nullableInteger = 42 - }), - JsonImplementation.NewtonsoftJson => JsonConvert.SerializeObject(new - { - FooBar = "foobar", - NullableInteger = 42 - }), - _ => throw new ArgumentOutOfRangeException(nameof(jsonImplementation)) - }; - _mockHttp .Expect(HttpMethod.Post, BaseUri) - .WithContent(expectedContent) + .WithContent("{\"FooBar\":\"foobar\",\"NullableInteger\":42}") .Respond(HttpStatusCode.OK); var client = CreateClient(jsonImplementation); @@ -78,24 +63,10 @@ public async Task TestSimpleValueObjectSerialization(JsonImplementation jsonImpl public async Task TestSimpleValueObjectDeserialization(JsonImplementation jsonImplementation) { // Arrange - var responseContent = jsonImplementation switch - { - JsonImplementation.SystemTextJson => System.Text.Json.JsonSerializer.Serialize(new - { - FooBar = "foobar", - NullableInteger = 42 - }), - JsonImplementation.NewtonsoftJson => JsonConvert.SerializeObject(new - { - FooBar = "foobar", - NullableInteger = 42 - }), - _ => throw new ArgumentOutOfRangeException(nameof(jsonImplementation)) - }; - _mockHttp .Expect(BaseUri) - .Respond(new StringContent(responseContent, Encoding.UTF8, "application/json")); + .Respond(new StringContent("{\"FooBar\":\"foobar\",\"NullableInteger\":42}", Encoding.UTF8, + "application/json")); var client = CreateClient(jsonImplementation);