diff --git a/docs/features/dependencyinjection.rst b/docs/features/dependencyinjection.rst index dbd3437f6..abf974de2 100644 --- a/docs/features/dependencyinjection.rst +++ b/docs/features/dependencyinjection.rst @@ -132,7 +132,14 @@ Current `implementation + { + op.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; + op.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString; + op.JsonSerializerOptions.WriteIndented = false; + op.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + op.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); + }); } The method cannot be overridden. It is not virtual, and there is no way to override current behavior by inheritance. @@ -154,74 +161,6 @@ The next section shows you an example of designing custom Ocelot pipeline by cus .. _di-custom-builder: -Custom Builder --------------- - -**Goal**: Replace ``Newtonsoft.Json`` services with ``System.Text.Json`` services. - -Problem -^^^^^^^ - -The main `AddOcelot`_ method adds -`Newtonsoft JSON `_ services -by the ``AddNewtonsoftJson`` extension method in default builder (`AddDefaultAspNetServices`_ method). -The ``AddNewtonsoftJson`` method calling was introduced in old .NET and Ocelot releases which was necessary when Microsoft did not launch the ``System.Text.Json`` library, -but now it affects normal use, so we have an intention to solve the problem. - -Modern `JSON services `_ -out of `the box `_ -will help to configure JSON settings by the ``JsonSerializerOptions`` property for JSON formatters during (de)serialization. - -Solution -^^^^^^^^ - -We have the following methods in `ServiceCollectionExtensions`_ class: - -.. code-block:: csharp - - IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, Func customBuilder); - IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, IConfiguration configuration, Func customBuilder); - -These methods with custom builder allow you to use your any desired JSON library for (de)serialization. -But we are going to create custom ``MvcCoreBuilder`` with support of JSON services, such as ``System.Text.Json``. -To do that we need to call ``AddJsonOptions`` extension of the ``MvcCoreMvcCoreBuilderExtensions`` class -(NuGet package: `Microsoft.AspNetCore.Mvc.Core `_) in **Startup.cs**: - -.. code-block:: csharp - - using Microsoft.Extensions.DependencyInjection; - using Ocelot.DependencyInjection; - using System.Reflection; - - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - services - .AddLogging() - .AddMiddlewareAnalysis() - .AddWebEncoders() - // Add your custom builder - .AddOcelotUsingBuilder(MyCustomBuilder); - } - - private static IMvcCoreBuilder MyCustomBuilder(IMvcCoreBuilder builder, Assembly assembly) - { - return builder - .AddApplicationPart(assembly) - .AddControllersAsServices() - .AddAuthorization() - - // Replace AddNewtonsoftJson() by AddJsonOptions() - .AddJsonOptions(options => - { - options.JsonSerializerOptions.WriteIndented = true; // use System.Text.Json - }); - } - } - -The sample code provides settings to render JSON as indented text rather than compressed plain JSON text without spaces. -This is just one common use case, and you can add additional services to the builder. ------------------------------------------------------------------ diff --git a/src/Ocelot.Provider.Consul/ConsulFileConfigurationRepository.cs b/src/Ocelot.Provider.Consul/ConsulFileConfigurationRepository.cs index c95146f46..b57c83bc3 100644 --- a/src/Ocelot.Provider.Consul/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot.Provider.Consul/ConsulFileConfigurationRepository.cs @@ -1,13 +1,14 @@ using Microsoft.Extensions.Options; -using Newtonsoft.Json; using Ocelot.Cache; using Ocelot.Configuration; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; +using Ocelot.Infrastructure; using Ocelot.Logging; using Ocelot.Provider.Consul.Interfaces; using Ocelot.Responses; using System.Text; +using System.Text.Json; namespace Ocelot.Provider.Consul; @@ -53,14 +54,14 @@ public async Task> Get() var bytes = queryResult.Response.Value; var json = Encoding.UTF8.GetString(bytes); - var consulConfig = JsonConvert.DeserializeObject(json); + var consulConfig = JsonSerializer.Deserialize(json, OcelotSerializerOptions.Web); return new OkResponse(consulConfig); } public async Task Set(FileConfiguration ocelotConfiguration) { - var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented); + var json = JsonSerializer.Serialize(ocelotConfiguration, OcelotSerializerOptions.WebWriteIndented); var bytes = Encoding.UTF8.GetBytes(json); var kvPair = new KVPair(_configurationKey) { diff --git a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs index 56933a5f2..41675e417 100644 --- a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs @@ -1,9 +1,10 @@ using Microsoft.AspNetCore.Hosting; -using Newtonsoft.Json; using Ocelot.Configuration.ChangeTracking; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Responses; +using System.Text.Json; using FileSys = System.IO.File; namespace Ocelot.Configuration.Repository; @@ -49,14 +50,14 @@ public Task> Get() jsonConfiguration = FileSys.ReadAllText(_environmentFile.FullName); } - var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); + var fileConfiguration = JsonSerializer.Deserialize(jsonConfiguration, OcelotSerializerOptions.Web); return Task.FromResult>(new OkResponse(fileConfiguration)); } public Task Set(FileConfiguration fileConfiguration) { - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.WebWriteIndented); lock (_lock) { diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs index cfea4dca5..9e40de50d 100644 --- a/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs @@ -1,8 +1,9 @@ using Microsoft.Extensions.Hosting; -using Newtonsoft.Json; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; +using Ocelot.Infrastructure; using Ocelot.Logging; +using System.Text.Json; namespace Ocelot.Configuration.Repository; @@ -68,7 +69,7 @@ private async Task Poll() if (fileConfig.IsError) { - _logger.LogWarning(() =>$"error geting file config, errors are {string.Join(',', fileConfig.Errors.Select(x => x.Message))}"); + _logger.LogWarning(() => $"error geting file config, errors are {string.Join(',', fileConfig.Errors.Select(x => x.Message))}"); return; } @@ -95,7 +96,7 @@ private async Task Poll() /// hash of the config. private static string ToJson(FileConfiguration config) { - var currentHash = JsonConvert.SerializeObject(config); + var currentHash = JsonSerializer.Serialize(config, OcelotSerializerOptions.Web); return currentHash; } diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index f07baba1a..2118200bf 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -1,9 +1,10 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Memory; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.Infrastructure; +using System.Text; +using System.Text.Json; namespace Ocelot.DependencyInjection; @@ -91,8 +92,8 @@ public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder private static IConfigurationBuilder ApplyMergeOcelotJsonOption(IConfigurationBuilder builder, MergeOcelotJson mergeTo, string json, string primaryConfigFile, bool? optional, bool? reloadOnChange) { - return mergeTo == MergeOcelotJson.ToMemory ? - builder.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) : + return mergeTo == MergeOcelotJson.ToMemory ? + builder.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(json))) : AddOcelotJsonFile(builder, json, primaryConfigFile, optional, reloadOnChange); } @@ -133,8 +134,8 @@ private static string GetMergedOcelotJson(string folder, IWebHostEnvironment env } var lines = File.ReadAllText(file.FullName); - var config = JsonConvert.DeserializeObject(lines); - if (file.Name.Equals(globalFileInfo.Name, StringComparison.OrdinalIgnoreCase) && + var config = JsonSerializer.Deserialize(lines, OcelotSerializerOptions.Web); + if (file.Name.Equals(globalFileInfo.Name, StringComparison.OrdinalIgnoreCase) && file.FullName.Equals(globalFileInfo.FullName, StringComparison.OrdinalIgnoreCase)) { fileConfiguration.GlobalConfiguration = config.GlobalConfiguration; @@ -144,8 +145,8 @@ private static string GetMergedOcelotJson(string folder, IWebHostEnvironment env fileConfiguration.Routes.AddRange(config.Routes); } - return JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); - } + return JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.WebWriteIndented); + } /// /// Adds Ocelot configuration by ready configuration object and writes JSON to the primary configuration file.
@@ -158,11 +159,11 @@ private static string GetMergedOcelotJson(string folder, IWebHostEnvironment env /// The 2nd argument of the AddJsonFile. /// The 3rd argument of the AddJsonFile. /// An object. - public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration, + public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, FileConfiguration fileConfiguration, string primaryConfigFile = null, bool? optional = null, bool? reloadOnChange = null) // optional injections { - var json = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); - return AddOcelotJsonFile(builder, json, primaryConfigFile, optional, reloadOnChange); + var json = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.WebWriteIndented); + return AddOcelotJsonFile(builder, json, primaryConfigFile, optional, reloadOnChange); } /// diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 5d152cd77..b0a0c9c22 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -36,6 +36,9 @@ using Ocelot.ServiceDiscovery.Providers; using Ocelot.WebSockets; using System.Reflection; +using System.Text.Encodings.Web; +using System.Text.Json.Serialization; +using System.Text.Unicode; namespace Ocelot.DependencyInjection; @@ -108,7 +111,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - + Services.TryAddSingleton(); Services.TryAddSingleton, OcelotConfigurationMonitor>(); @@ -152,7 +155,6 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo /// /// Adds default ASP.NET services which are the minimal part of the gateway core. /// - /// Finally the builder adds Newtonsoft.Json services via the extension-method.
/// To remove these services, use custom builder in the extension-method. ///
///
@@ -163,11 +165,10 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo /// -
/// - . /// - /// Warning! The following extensions being called:
+ /// Warning! The following extensions being called
/// -
/// -
- /// -
- /// - , removable. + /// - . ///
/// /// The default builder being returned by extension-method. @@ -184,7 +185,14 @@ protected IMvcCoreBuilder AddDefaultAspNetServices(IMvcCoreBuilder builder, Asse .AddApplicationPart(assembly) .AddControllersAsServices() .AddAuthorization() - .AddNewtonsoftJson(); + .AddJsonOptions(op => + { + op.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; + op.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString; + op.JsonSerializerOptions.WriteIndented = false; + op.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + op.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); + }); } public IOcelotBuilder AddSingletonDefinedAggregator() diff --git a/src/Ocelot/Infrastructure/OcelotSerializerOptions.cs b/src/Ocelot/Infrastructure/OcelotSerializerOptions.cs new file mode 100644 index 000000000..6f40987ab --- /dev/null +++ b/src/Ocelot/Infrastructure/OcelotSerializerOptions.cs @@ -0,0 +1,109 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Unicode; + +namespace Ocelot.Infrastructure; + +public static class OcelotSerializerOptions +{ + public static readonly JsonSerializerOptions Web = new() + { + Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), + NumberHandling = JsonNumberHandling.AllowReadingFromString, + PropertyNameCaseInsensitive = true, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + WriteIndented = false, + }; + + public static readonly JsonSerializerOptions WebWriteIndented = new() + { + Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), + NumberHandling = JsonNumberHandling.AllowReadingFromString, + PropertyNameCaseInsensitive = true, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + WriteIndented = true, + }; + + public static List ExtractValuesFromJsonPath(this JsonDocument document, string jsonPath) + { + var root = document.RootElement; + + var pathParts = jsonPath.Trim('$', '.').Replace("[*]", "").Trim().Split('.'); + + var elements = new List(); + + TraverseJsonPath(root, pathParts, 0, elements); + + return elements; + } + + private static void TraverseJsonPath(JsonElement currentElement, string[] pathParts, int index, List elements) + { + if (index >= pathParts.Length) + { + return; + } + + var part = pathParts[index]; + + if (currentElement.ValueKind == JsonValueKind.Array) + { + foreach (var element in currentElement.EnumerateArray()) + { + if (element.ValueKind == JsonValueKind.Object) + { + if (element.TryGetProperty(part, out JsonElement nextElement)) + { + if (nextElement.ValueKind == JsonValueKind.Array) + { + TraverseJsonPath(nextElement, pathParts, 0, elements); + } + else + { + var item = nextElement.ToString(); + if (!string.IsNullOrWhiteSpace(item)) + { + elements.Add(item); + } + } + } + else + { + TraverseJsonPath(element, pathParts, index + 1, elements); + } + } + else + { + TraverseJsonPath(element, pathParts, index + 1, elements); + } + } + } + else if (currentElement.ValueKind == JsonValueKind.Object) + { + if (currentElement.TryGetProperty(part, out JsonElement nextElement)) + { + if (nextElement.ValueKind == JsonValueKind.Array) + { + TraverseJsonPath(nextElement, pathParts, index + 1, elements); + } + else + { + var item = nextElement.ToString(); + if (!string.IsNullOrWhiteSpace(item)) + { + elements.Add(item); + } + } + } + } + else + { + var item = currentElement.ToString(); + if (!string.IsNullOrWhiteSpace(item)) + { + elements.Add(item); + } + } + } +} diff --git a/src/Ocelot/Metadata/DownstreamRouteExtensions.cs b/src/Ocelot/Metadata/DownstreamRouteExtensions.cs index ca3ba25e6..2d62f7f8c 100644 --- a/src/Ocelot/Metadata/DownstreamRouteExtensions.cs +++ b/src/Ocelot/Metadata/DownstreamRouteExtensions.cs @@ -1,6 +1,6 @@ using Ocelot.Configuration; +using Ocelot.Infrastructure; using System.Globalization; -using System.Reflection; using System.Text.Json; namespace Ocelot.Metadata; @@ -74,7 +74,7 @@ public static T GetMetadata(this DownstreamRoute downstreamRoute, string key, } return (T)ConvertTo(typeof(T), metadataValue, downstreamRoute.MetadataOptions, - jsonSerializerOptions ?? new JsonSerializerOptions(JsonSerializerDefaults.Web)); + jsonSerializerOptions ?? OcelotSerializerOptions.Web); } /// diff --git a/src/Ocelot/Multiplexer/MultiplexingMiddleware.cs b/src/Ocelot/Multiplexer/MultiplexingMiddleware.cs index c148eb9b9..f5cce0d81 100644 --- a/src/Ocelot/Multiplexer/MultiplexingMiddleware.cs +++ b/src/Ocelot/Multiplexer/MultiplexingMiddleware.cs @@ -1,12 +1,13 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; -using Newtonsoft.Json.Linq; using Ocelot.Configuration; using Ocelot.Configuration.File; using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Infrastructure; using Ocelot.Logging; using Ocelot.Middleware; using System.Collections; +using System.Text.Json; using Route = Ocelot.Configuration.Route; namespace Ocelot.Multiplexer; @@ -132,14 +133,14 @@ protected virtual async Task ProcessRoutesWithRouteKeysAsync(Http { var processing = new List>(); var content = await mainResponse.Items.DownstreamResponse().Content.ReadAsStringAsync(); - var jObject = JToken.Parse(content); - + using var document = JsonDocument.Parse(content); + foreach (var downstreamRoute in routes.Skip(1)) { var matchAdvancedAgg = routeKeysConfigs.FirstOrDefault(q => q.RouteKey == downstreamRoute.Key); if (matchAdvancedAgg != null) { - processing.AddRange(ProcessRouteWithComplexAggregation(matchAdvancedAgg, jObject, context, downstreamRoute)); + processing.AddRange(ProcessRouteWithComplexAggregation(matchAdvancedAgg, document, context, downstreamRoute)); continue; } @@ -162,11 +163,14 @@ private Task MapResponsesAsync(HttpContext context, Route route, HttpContext mai /// /// Processing a route with aggregation. /// - private IEnumerable> ProcessRouteWithComplexAggregation(AggregateRouteConfig matchAdvancedAgg, - JToken jObject, HttpContext httpContext, DownstreamRoute downstreamRoute) + private IEnumerable> ProcessRouteWithComplexAggregation(AggregateRouteConfig matchAdvancedAgg, + JsonDocument document, HttpContext httpContext, DownstreamRoute downstreamRoute) { - var processing = new List>(); - var values = jObject.SelectTokens(matchAdvancedAgg.JsonPath).Select(s => s.ToString()).Distinct(); + var processing = new List>(); + + var values = document.ExtractValuesFromJsonPath(matchAdvancedAgg.JsonPath) + .Distinct(); + foreach (var value in values) { var tPnv = httpContext.Items.TemplatePlaceholderNameAndValues(); @@ -175,8 +179,8 @@ private IEnumerable> ProcessRouteWithComplexAggregation(Aggreg } return processing; - } - + } + /// /// Process a downstream route asynchronously. /// diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index eafb4a446..a0fc7411a 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -42,12 +42,12 @@ - + - + diff --git a/src/Ocelot/RateLimiting/DistributedCacheRateLimitStorage.cs b/src/Ocelot/RateLimiting/DistributedCacheRateLimitStorage.cs index b7fb79de3..f1934a903 100644 --- a/src/Ocelot/RateLimiting/DistributedCacheRateLimitStorage.cs +++ b/src/Ocelot/RateLimiting/DistributedCacheRateLimitStorage.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Caching.Distributed; -using Newtonsoft.Json; +using Ocelot.Infrastructure; +using System.Text.Json; namespace Ocelot.RateLimiting; @@ -16,7 +17,7 @@ public class DistributedCacheRateLimitStorage : IRateLimitStorage public DistributedCacheRateLimitStorage(IDistributedCache memoryCache) => _memoryCache = memoryCache; public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) - => _memoryCache.SetString(id, JsonConvert.SerializeObject(counter), new DistributedCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); + => _memoryCache.SetString(id, JsonSerializer.Serialize(counter, OcelotSerializerOptions.Web), new DistributedCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); public bool Exists(string id) => !string.IsNullOrEmpty(_memoryCache.GetString(id)); @@ -24,7 +25,7 @@ public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) { var stored = _memoryCache.GetString(id); return !string.IsNullOrEmpty(stored) - ? JsonConvert.DeserializeObject(stored) + ? JsonSerializer.Deserialize(stored, OcelotSerializerOptions.Web) : null; } diff --git a/src/Ocelot/RateLimiting/RateLimitCounter.cs b/src/Ocelot/RateLimiting/RateLimitCounter.cs index 2507a0433..3bcbe78d6 100644 --- a/src/Ocelot/RateLimiting/RateLimitCounter.cs +++ b/src/Ocelot/RateLimiting/RateLimitCounter.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Ocelot.RateLimiting; diff --git a/test/Ocelot.AcceptanceTests/BearerToken.cs b/test/Ocelot.AcceptanceTests/BearerToken.cs index 48fca2fad..75e56008a 100644 --- a/test/Ocelot.AcceptanceTests/BearerToken.cs +++ b/test/Ocelot.AcceptanceTests/BearerToken.cs @@ -1,15 +1,15 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Ocelot.AcceptanceTests; internal class BearerToken { - [JsonProperty("access_token")] + [JsonPropertyName("access_token")] public string AccessToken { get; set; } - [JsonProperty("expires_in")] + [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } - [JsonProperty("token_type")] + [JsonPropertyName("token_type")] public string TokenType { get; set; } } diff --git a/test/Ocelot.AcceptanceTests/Caching/CachingTests.cs b/test/Ocelot.AcceptanceTests/Caching/CachingTests.cs index 709ca97c8..10b50e3cc 100644 --- a/test/Ocelot.AcceptanceTests/Caching/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/Caching/CachingTests.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; +using Ocelot.Infrastructure; using System.Text; using JsonSerializer = System.Text.Json.JsonSerializer; @@ -298,7 +299,7 @@ private void ThenTheCounterValueShouldBe(int expected) LastName = "Test", }; - var testBody1String = JsonSerializer.Serialize(testBody1); + var testBody1String = JsonSerializer.Serialize(testBody1, OcelotSerializerOptions.Web); var testBody2 = new TestBody { @@ -308,7 +309,7 @@ private void ThenTheCounterValueShouldBe(int expected) LastName = "Test", }; - var testBody2String = JsonSerializer.Serialize(testBody2); + var testBody2String = JsonSerializer.Serialize(testBody2, OcelotSerializerOptions.Web); return (testBody1String, testBody2String); } diff --git a/test/Ocelot.AcceptanceTests/Configuration/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/Configuration/ConfigurationInConsulTests.cs index e61fdebe3..4daa932f9 100644 --- a/test/Ocelot.AcceptanceTests/Configuration/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/Configuration/ConfigurationInConsulTests.cs @@ -7,14 +7,15 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Newtonsoft.Json; using Ocelot.AcceptanceTests.Caching; using Ocelot.Cache.CacheManager; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Middleware; using Ocelot.Provider.Consul; using System.Text; +using System.Text.Json; namespace Ocelot.AcceptanceTests.Configuration; @@ -130,7 +131,7 @@ private Task GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string { if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") { - var json = JsonConvert.SerializeObject(_config); + var json = JsonSerializer.Serialize(_config, OcelotSerializerOptions.Web); var bytes = Encoding.UTF8.GetBytes(json); @@ -150,9 +151,9 @@ private Task GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string // var json = reader.ReadToEnd(); var json = await reader.ReadToEndAsync(); - _config = JsonConvert.DeserializeObject(json); + _config = JsonSerializer.Deserialize(json, OcelotSerializerOptions.Web); - var response = JsonConvert.SerializeObject(true); + var response = JsonSerializer.Serialize(true, OcelotSerializerOptions.Web); await context.Response.WriteAsync(response); } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulConfigurationInConsulTests.cs index bfe7d345b..7017c4ecb 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulConfigurationInConsulTests.cs @@ -4,14 +4,15 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; using Ocelot.AcceptanceTests.RateLimiting; using Ocelot.Cache; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Middleware; using Ocelot.Provider.Consul; using System.Text; +using System.Text.Json; namespace Ocelot.AcceptanceTests.ServiceDiscovery; @@ -386,14 +387,14 @@ private Task GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string { if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") { - var json = JsonConvert.SerializeObject(_config); + var json = JsonSerializer.Serialize(_config, OcelotSerializerOptions.Web); var bytes = Encoding.UTF8.GetBytes(json); var base64 = Convert.ToBase64String(bytes); var kvp = new FakeConsulGetResponse(base64); - json = JsonConvert.SerializeObject(new[] { kvp }); + json = JsonSerializer.Serialize(new[] { kvp }, OcelotSerializerOptions.Web); context.Response.Headers.Append("Content-Type", "application/json"); await context.Response.WriteAsync(json); } @@ -407,9 +408,9 @@ private Task GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string // var json = reader.ReadToEnd(); var json = await reader.ReadToEndAsync(); - _config = JsonConvert.DeserializeObject(json); + _config = JsonSerializer.Deserialize(json, OcelotSerializerOptions.Web); - var response = JsonConvert.SerializeObject(true); + var response = JsonSerializer.Serialize(true, OcelotSerializerOptions.Web); await context.Response.WriteAsync(response); } @@ -421,7 +422,7 @@ private Task GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string } else if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") { - var json = JsonConvert.SerializeObject(_consulServices); + var json = JsonSerializer.Serialize(_consulServices, OcelotSerializerOptions.Web); context.Response.Headers.Append("Content-Type", "application/json"); await context.Response.WriteAsync(json); } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulServiceDiscoveryTests.cs index 9a49883b1..561a02153 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulServiceDiscoveryTests.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; using Ocelot.AcceptanceTests.LoadBalancer; using Ocelot.Configuration; using Ocelot.Configuration.File; @@ -14,6 +13,7 @@ using Ocelot.Provider.Consul.Interfaces; using Ocelot.ServiceDiscovery.Providers; using System.Runtime.CompilerServices; +using System.Text.Json; using System.Text.RegularExpressions; namespace Ocelot.AcceptanceTests.ServiceDiscovery; @@ -619,7 +619,8 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) // Use the parsed service name to filter the registered Consul services var serviceName = pathMatch.Groups["serviceName"].Value; var services = _consulServices.Where(x => x.Service.Service == serviceName).ToList(); - var json = JsonConvert.SerializeObject(services); + var json = JsonSerializer.Serialize(services, OcelotSerializerOptions.Web); + json = json.Replace("\"Name\":", "\"Node\":"); //} context.Response.Headers.Append("Content-Type", "application/json"); @@ -631,7 +632,7 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) { //_counterNodes++; int count = Interlocked.Increment(ref _counterNodes); - var json = JsonConvert.SerializeObject(_consulNodes); + var json = JsonSerializer.Serialize(_consulNodes, OcelotSerializerOptions.Web); context.Response.Headers.Append("Content-Type", "application/json"); await context.Response.WriteAsync(json); } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulWebSocketTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulWebSocketTests.cs index 5be6377d7..7bdd94d1d 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulWebSocketTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscovery/ConsulWebSocketTests.cs @@ -5,14 +5,15 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Middleware; using Ocelot.Provider.Consul; using Ocelot.WebSockets; using System.Net.WebSockets; using System.Text; +using System.Text.Json; namespace Ocelot.AcceptanceTests.ServiceDiscovery; @@ -174,7 +175,7 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider(int port, string se { if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") { - var json = JsonConvert.SerializeObject(_serviceEntries); + var json = JsonSerializer.Serialize(_serviceEntries, OcelotSerializerOptions.Web); context.Response.Headers.Append("Content-Type", "application/json"); await context.Response.WriteAsync(json); } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscovery/EurekaServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscovery/EurekaServiceDiscoveryTests.cs index 5e4b53b9f..dd499cc81 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscovery/EurekaServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscovery/EurekaServiceDiscoveryTests.cs @@ -1,8 +1,10 @@ using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; using Ocelot.Configuration.File; +using Ocelot.Infrastructure; using Ocelot.LoadBalancer.LoadBalancers; using Steeltoe.Common.Discovery; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Ocelot.AcceptanceTests.ServiceDiscovery; @@ -142,7 +144,7 @@ private void GivenThereIsAFakeEurekaServiceDiscoveryProvider(string url, string }, }; - var json = JsonConvert.SerializeObject(applications); + var json = JsonSerializer.Serialize(applications, OcelotSerializerOptions.Web); context.Response.Headers.Append("Content-Type", "application/json"); await context.Response.WriteAsync(json); } @@ -194,25 +196,25 @@ public FakeEurekaService(string serviceId, string host, int port, bool isSecure, public class Port { - [JsonProperty("$")] + [JsonPropertyName("$")] public int value { get; set; } - [JsonProperty("@enabled")] + [JsonPropertyName("@enabled")] public string enabled { get; set; } } public class SecurePort { - [JsonProperty("$")] + [JsonPropertyName("$")] public int value { get; set; } - [JsonProperty("@enabled")] + [JsonPropertyName("@enabled")] public string enabled { get; set; } } public class DataCenterInfo { - [JsonProperty("@class")] + [JsonPropertyName("@class")] public string value { get; set; } public string name { get; set; } @@ -235,7 +237,7 @@ public class LeaseInfo public class ValueMetadata { - [JsonProperty("@class")] + [JsonPropertyName("@class")] public string value { get; set; } } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscovery/KubernetesServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscovery/KubernetesServiceDiscoveryTests.cs index 7350d5ba7..901de9f57 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscovery/KubernetesServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscovery/KubernetesServiceDiscoveryTests.cs @@ -3,11 +3,11 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Newtonsoft.Json; using Ocelot.AcceptanceTests.LoadBalancer; using Ocelot.Configuration; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; using Ocelot.Provider.Kubernetes; @@ -15,6 +15,7 @@ using Ocelot.ServiceDiscovery.Providers; using Ocelot.Values; using System.Runtime.CompilerServices; +using System.Text.Json; namespace Ocelot.AcceptanceTests.ServiceDiscovery; @@ -294,7 +295,7 @@ private void GivenThereIsAFakeKubernetesProvider(EndpointsV1 endpoints, bool isS } endpoints.Metadata.Generation = _k8sServiceGeneration; - json = JsonConvert.SerializeObject(endpoints); + json = JsonSerializer.Serialize(endpoints, OcelotSerializerOptions.Web); } if (context.Request.Headers.TryGetValue("Authorization", out var values)) diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 1de34abb3..710a0c0e3 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Ocelot.AcceptanceTests.Caching; using Ocelot.AcceptanceTests.Properties; using Ocelot.Cache.CacheManager; @@ -17,6 +16,8 @@ using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; using Ocelot.Middleware; using Ocelot.Provider.Eureka; @@ -27,7 +28,8 @@ using Serilog.Core; using System.IO.Compression; using System.Net.Http.Headers; -using System.Text; +using System.Text; +using System.Text.Json; using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using CookieHeaderValue = Microsoft.Net.Http.Headers.CookieHeaderValue; @@ -58,7 +60,7 @@ public Steps() : base() { _random = new Random(); _testId = Guid.NewGuid(); - _ocelotConfigFileName = $"{_testId:N}-{ConfigurationBuilderExtensions.PrimaryConfigFile}"; + _ocelotConfigFileName = $"{_testId:N}-{ConfigurationBuilderExtensions.PrimaryConfigFile}"; Files = new() { _ocelotConfigFileName }; Folders = new(); } @@ -74,8 +76,8 @@ public Steps() : base() protected static FileConfiguration GivenConfiguration(params FileRoute[] routes) => new() { Routes = new(routes), - }; - + }; + protected static FileRoute GivenDefaultRoute(int port) => new() { DownstreamPathTemplate = "/", @@ -108,7 +110,7 @@ public async Task ThenConfigShouldBeWithTimeout(FileConfiguration fileConfig, in }); result.ShouldBe(true); } - + /// /// TODO Move to . See references. /// @@ -147,9 +149,9 @@ public async Task StartFakeOcelotWithWebSockets() _ocelotHost = _ocelotBuilder.Build(); await _ocelotHost.StartAsync(); } - + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - => GivenThereIsAConfiguration(fileConfiguration, _ocelotConfigFileName); + => GivenThereIsAConfiguration(fileConfiguration, _ocelotConfigFileName); public void GivenThereIsAConfiguration(FileConfiguration from, string toFile) { var json = SerializeJson(from, ref toFile); @@ -162,10 +164,10 @@ public Task GivenThereIsAConfigurationAsync(FileConfiguration from, string toFil } protected string SerializeJson(FileConfiguration from, ref string toFile) { - toFile ??= _ocelotConfigFileName; - Files.Add(toFile); // register for disposing - return JsonConvert.SerializeObject(from, Formatting.Indented); - } + toFile ??= _ocelotConfigFileName; + Files.Add(toFile); // register for disposing + return JsonSerializer.Serialize(from, OcelotSerializerOptions.WebWriteIndented); + } protected virtual void DeleteFiles() { @@ -199,12 +201,12 @@ protected virtual void DeleteFolders() f.Delete(true); } } - catch (Exception e) - { - Console.WriteLine(e); - } + catch (Exception e) + { + Console.WriteLine(e); + } } - } + } public void ThenTheResponseBodyHeaderIs(string key, string value) { @@ -477,20 +479,20 @@ public async Task WhenIGetUrlOnTheApiGatewayWithCookie(string url, string cookie => _response = await WhenIGetUrlOnTheApiGateway(url, cookie, value); public async Task WhenIGetUrlOnTheApiGatewayWithCookie(string url, CookieHeaderValue cookie) => _response = await WhenIGetUrlOnTheApiGateway(url, cookie); - + public Task WhenIGetUrlOnTheApiGateway(string url, string cookie, string value) { var header = new CookieHeaderValue(cookie, value); return WhenIGetUrlOnTheApiGateway(url, header); - } - + } + public Task WhenIGetUrlOnTheApiGateway(string url, CookieHeaderValue cookie) - { + { var requestMessage = new HttpRequestMessage(HttpMethod.Get, url); requestMessage.Headers.Add("Cookie", cookie.ToString()); return _ocelotClient.SendAsync(requestMessage); - } - + } + // END of Cookies helpers /// @@ -530,7 +532,7 @@ public void ThenTheResponseHeaderIs(string key, string value) public void ThenTheReasonPhraseIs(string expected) { _response.ReasonPhrase.ShouldBe(expected); - } + } public void GivenOcelotIsRunningWithServices(Action configureServices) => GivenOcelotIsRunningWithServices(configureServices, null); @@ -617,7 +619,7 @@ internal async Task GivenIHaveATokenWithForm(string url, IEnumerabl var response = await httpClient.PostAsync(tokenUrl, content); var responseContent = await response.Content.ReadAsStringAsync(); response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); + _token = JsonSerializer.Deserialize(responseContent, OcelotSerializerOptions.Web); return _token; } @@ -721,18 +723,18 @@ public void GivenIAddAHeader(string key, string value) { _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(key, value); } - + public static void WhenIDoActionMultipleTimes(int times, Action action) - { + { for (int i = 0; i < times; i++) action?.Invoke(i); - } - + } + public static async Task WhenIDoActionMultipleTimes(int times, Func action) - { + { for (int i = 0; i < times; i++) await action.Invoke(i); - } + } public async Task WhenIGetUrlOnTheApiGateway(string url, string requestId) { @@ -757,7 +759,7 @@ public void GivenThePostHasContentType(string postContent) public void GivenThePostHasGzipContent(object input) { - var json = JsonConvert.SerializeObject(input); + var json = JsonSerializer.Serialize(input, OcelotSerializerOptions.Web); var jsonBytes = Encoding.UTF8.GetBytes(json); var ms = new MemoryStream(); using (var gzip = new GZipStream(ms, CompressionMode.Compress, true)) @@ -772,12 +774,12 @@ public void GivenThePostHasGzipContent(object input) _postContent = content; } - public void ThenTheResponseBodyShouldBe(string expectedBody) + public void ThenTheResponseBodyShouldBe(string expectedBody) => _response.Content.ReadAsStringAsync().GetAwaiter().GetResult().ShouldBe(expectedBody); public void ThenTheResponseBodyShouldBe(string expectedBody, string customMessage) => _response.Content.ReadAsStringAsync().GetAwaiter().GetResult().ShouldBe(expectedBody, customMessage); - public void ThenTheContentLengthIs(int expected) + public void ThenTheContentLengthIs(int expected) => _response.Content.Headers.ContentLength.ShouldBe(expected); public void ThenTheStatusCodeShouldBe(HttpStatusCode expected) @@ -785,10 +787,10 @@ public void ThenTheStatusCodeShouldBe(HttpStatusCode expected) public void ThenTheStatusCodeShouldBe(int expected) => ((int)_response.StatusCode).ShouldBe(expected); - public void ThenTheRequestIdIsReturned() + public void ThenTheRequestIdIsReturned() => _response.Headers.GetValues(RequestIdKey).First().ShouldNotBeNullOrEmpty(); - public void ThenTheRequestIdIsReturned(string expected) + public void ThenTheRequestIdIsReturned(string expected) => _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); public void WhenIMakeLotsOfDifferentRequestsToTheApiGateway() @@ -970,7 +972,7 @@ protected virtual void Dispose(bool disposing) } if (disposing) - { + { _ocelotClient?.Dispose(); _ocelotServer?.Dispose(); _ocelotHost?.Dispose(); diff --git a/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs b/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs index 78ba7d22d..fb55d92fd 100644 --- a/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs +++ b/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs @@ -1,7 +1,8 @@ using Consul; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; -using Ocelot.Configuration.File; +using Ocelot.Configuration.File; +using Ocelot.Infrastructure; +using System.Text.Json; namespace Ocelot.AcceptanceTests; @@ -96,7 +97,7 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) { if (context.Request.Path.Value == "/v1/health/service/product") { - var json = JsonConvert.SerializeObject(_serviceEntries); + var json = JsonSerializer.Serialize(_serviceEntries, OcelotSerializerOptions.Web); context.Response.Headers.Append("Content-Type", "application/json"); await context.Response.WriteAsync(json); } diff --git a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs index fec5d9e5d..b8506b3b8 100644 --- a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs +++ b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs @@ -3,10 +3,11 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; -using Ocelot.Middleware; +using Ocelot.Infrastructure; +using Ocelot.Middleware; +using System.Text.Json; namespace Ocelot.Benchmarks; @@ -112,7 +113,7 @@ public static void GivenThereIsAConfiguration(FileConfiguration fileConfiguratio { var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json"); - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.Web); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.Benchmarks/JsonSerializerBenchmark.cs b/test/Ocelot.Benchmarks/JsonSerializerBenchmark.cs new file mode 100644 index 000000000..37c6f3036 --- /dev/null +++ b/test/Ocelot.Benchmarks/JsonSerializerBenchmark.cs @@ -0,0 +1,87 @@ +using BenchmarkDotNet.Jobs; +using Bogus; +using Newtonsoft.Json; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Ocelot.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net80)] +[Config(typeof(JsonSerializerBenchmark))] +public class JsonSerializerBenchmark : ManualConfig +{ + private string _serializedTestUsers; + + private List _testUsers = new(); + + [Params(1000)] + public int Count { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + Faker faker = new Faker().CustomInstantiator(f => new User + { + UserId = Guid.NewGuid(), + FirstName = f.Name.FirstName(), + LastName = f.Name.LastName(), + FullName = f.Name.FullName(), + Username = f.Internet.UserName(f.Name.FirstName(), f.Name.LastName()), + Email = f.Internet.Email(f.Name.FirstName(), f.Name.LastName()), + }); + _testUsers = faker.Generate(Count); + _serializedTestUsers = JsonSerializer.Serialize(_testUsers); + } + + [Benchmark] + [BenchmarkCategory("Serialize", "Newtonsoft")] + public void NewtonsoftSerializeBigData() + { + _ = JsonConvert.SerializeObject(_testUsers); + } + + [Benchmark] + [BenchmarkCategory("Serialize", "Microsoft")] + public void MicrosoftSerializeBigData() + { + _ = JsonSerializer.Serialize(_testUsers); + } + + [Benchmark] + [BenchmarkCategory("Deserialize", "Newtonsoft")] + public void NewtonsoftDeserializeBigData() + { + _ = JsonConvert.DeserializeObject>(_serializedTestUsers); + } + + [Benchmark] + [BenchmarkCategory("Deserialize", "Microsoft")] + public void MicrosoftDeserializeBigData() + { + _ = JsonSerializer.Deserialize>(_serializedTestUsers); + } +} + +//BenchmarkDotNet v0.13.11, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3) +//Intel Core i7-10870H CPU 2.20GHz, 1 CPU, 16 logical and 8 physical cores +// .NET SDK 8.0.303 +// [Host] : .NET 6.0.32 (6.0.3224.31407), X64 RyuJIT AVX2[AttachedDebugger] +// .NET 8.0 : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2 + +// Job =.NET 8.0 Runtime=.NET 8.0 + +// | Method | Count | Mean | Error | StdDev | Median | Op/s | Gen0 | Gen1 | Gen2 | Allocated | +// |----------------------------- |------ |-----------:|---------:|----------:|-----------:|--------:|---------:|---------:|---------:|----------:| +// | MicrosoftDeserializeBigData | 1000 | 856.3 us | 53.98 us | 157.47 us | 797.1 us | 1,167.8 | 39.0625 | 13.6719 | - | 328.78 KB | +// | NewtonsoftDeserializeBigData | 1000 | 1,137.2 us | 18.74 us | 17.53 us | 1,132.8 us | 879.4 | 54.6875 | 17.5781 | - | 457.94 KB | +// |==============================================================================================================================================| +// | MicrosoftSerializeBigData | 1000 | 646.4 us | 12.72 us | 20.90 us | 645.7 us | 1,546.9 | 110.3516 | 110.3516 | 110.3516 | 350.02 KB | +// | NewtonsoftSerializeBigData | 1000 | 1,033.4 us | 19.37 us | 42.53 us | 1,022.8 us | 967.7 | 109.3750 | 109.3750 | 109.3750 | 837.82 KB | +public class User +{ + public Guid UserId { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + public string Username { get; set; } + public string Email { get; set; } +} diff --git a/test/Ocelot.Benchmarks/MsLoggerBenchmarks.cs b/test/Ocelot.Benchmarks/MsLoggerBenchmarks.cs index b8b53ff1b..dcda051cd 100644 --- a/test/Ocelot.Benchmarks/MsLoggerBenchmarks.cs +++ b/test/Ocelot.Benchmarks/MsLoggerBenchmarks.cs @@ -5,11 +5,12 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Logging; using Ocelot.Middleware; +using System.Text.Json; namespace Ocelot.Benchmarks; @@ -162,7 +163,7 @@ public static void GivenThereIsAConfiguration(FileConfiguration fileConfiguratio { var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json"); - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.Web); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj index 378ed72e5..21d46e681 100644 --- a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj +++ b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj @@ -25,8 +25,11 @@ + + all + diff --git a/test/Ocelot.Benchmarks/PayloadBenchmarks.cs b/test/Ocelot.Benchmarks/PayloadBenchmarks.cs index aa967d16f..8db6ac2e2 100644 --- a/test/Ocelot.Benchmarks/PayloadBenchmarks.cs +++ b/test/Ocelot.Benchmarks/PayloadBenchmarks.cs @@ -4,14 +4,15 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Middleware; using System.Diagnostics; using System.Net.Http.Headers; using System.Reflection; using System.Text; +using System.Text.Json; namespace Ocelot.Benchmarks; @@ -112,7 +113,7 @@ private static string GetBaseDirectory() private static object[] GeneratePayload(int size, string directory, string fileName, bool isJson) { var filePath = Path.Combine(directory, fileName); - var generateDummy = isJson ? (Func) GenerateDummyJsonFile : GenerateDummyDatFile; + var generateDummy = isJson ? (Func)GenerateDummyJsonFile : GenerateDummyDatFile; return new object[] { generateDummy(size, filePath), @@ -220,7 +221,7 @@ private void GivenOcelotIsRunning(string url) public static void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { var configurationPath = Path.Combine(AppContext.BaseDirectory, ConfigurationBuilderExtensions.PrimaryConfigFile); - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.Web); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.Benchmarks/Program.cs b/test/Ocelot.Benchmarks/Program.cs index bea66ff17..689d1fea6 100644 --- a/test/Ocelot.Benchmarks/Program.cs +++ b/test/Ocelot.Benchmarks/Program.cs @@ -17,7 +17,15 @@ public static void Main(string[] args) typeof(MsLoggerBenchmarks), typeof(PayloadBenchmarks), typeof(ResponseBenchmarks), + typeof(JsonSerializerBenchmark), }); - switcher.Run(args); + + var config = ManualConfig.Create(DefaultConfig.Instance) + .AddAnalyser(BenchmarkDotNet.Analysers.EnvironmentAnalyser.Default) + .AddExporter(BenchmarkDotNet.Exporters.MarkdownExporter.GitHub) + .AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default) + .AddColumn(StatisticColumn.OperationsPerSecond); + + switcher.Run(args, config); } } diff --git a/test/Ocelot.Benchmarks/ResponseBenchmarks.cs b/test/Ocelot.Benchmarks/ResponseBenchmarks.cs index 53788b140..50f67f96e 100644 --- a/test/Ocelot.Benchmarks/ResponseBenchmarks.cs +++ b/test/Ocelot.Benchmarks/ResponseBenchmarks.cs @@ -4,15 +4,15 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Middleware; using System.Diagnostics; using System.Net.Http.Headers; using System.Reflection; using System.Text; -using Ocelot.Responses; +using System.Text.Json; namespace Ocelot.Benchmarks; @@ -222,7 +222,7 @@ private void GivenOcelotIsRunning(string url) public static void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { var configurationPath = Path.Combine(AppContext.BaseDirectory, ConfigurationBuilderExtensions.PrimaryConfigFile); - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.Web); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.Benchmarks/SerilogBenchmarks.cs b/test/Ocelot.Benchmarks/SerilogBenchmarks.cs index cd2adcb98..6644faae9 100644 --- a/test/Ocelot.Benchmarks/SerilogBenchmarks.cs +++ b/test/Ocelot.Benchmarks/SerilogBenchmarks.cs @@ -5,13 +5,14 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Logging; using Ocelot.Middleware; using Serilog; using Serilog.Core; +using System.Text.Json; namespace Ocelot.Benchmarks; @@ -193,7 +194,7 @@ public static void GivenThereIsAConfiguration(FileConfiguration fileConfiguratio { var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json"); - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.Web); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index b0e23657e..5c714272e 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -8,16 +8,16 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; using Ocelot.Administration; -using Ocelot.Cache; using Ocelot.Configuration.ChangeTracking; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Middleware; using System.Net; using System.Net.Http.Headers; using System.Reflection; +using System.Text.Json; namespace Ocelot.IntegrationTests; @@ -342,12 +342,12 @@ private static void ThenTheConfigurationIsSavedCorrectly(FileConfiguration expec { var ocelotJsonPath = $"{AppContext.BaseDirectory}ocelot.json"; var resultText = File.ReadAllText(ocelotJsonPath); - var expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); + var expectedText = JsonSerializer.Serialize(expected, OcelotSerializerOptions.WebWriteIndented); resultText.ShouldBe(expectedText); var environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot.Production.json"; resultText = File.ReadAllText(environmentSpecificPath); - expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); + expectedText = JsonSerializer.Serialize(expected, OcelotSerializerOptions.WebWriteIndented); resultText.ShouldBe(expectedText); } @@ -523,7 +523,7 @@ private async Task GivenIHaveAToken(string url) var response = await httpClient.PostAsync($"{url}/connect/token", content); var responseContent = await response.Content.ReadAsStringAsync(); response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); + _token = JsonSerializer.Deserialize(responseContent, OcelotSerializerOptions.Web); } private async Task GivenThereIsAnIdentityServerOn(string url, string apiName) @@ -640,7 +640,7 @@ private async Task WhenIGetUrlOnTheSecondOcelot(string url) private async Task WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) { - var json = JsonConvert.SerializeObject(updatedConfiguration); + var json = JsonSerializer.Serialize(updatedConfiguration, OcelotSerializerOptions.Web); var content = new StringContent(json); content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); _response = await _httpClient.PostAsync(url, content); @@ -654,7 +654,7 @@ private async Task ThenTheResponseBodyShouldBe(string expected) private async Task ThenTheResponseShouldBe(FileConfiguration expecteds) { - var response = JsonConvert.DeserializeObject(await _response.Content.ReadAsStringAsync()); + var response = JsonSerializer.Deserialize(await _response.Content.ReadAsStringAsync(), OcelotSerializerOptions.Web); response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); response.GlobalConfiguration.ServiceDiscoveryProvider.Scheme.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Scheme); @@ -698,7 +698,7 @@ private async Task GivenIHaveAnOcelotToken(string adminPath) var response = await _httpClient.PostAsync(tokenUrl, content); var responseContent = await response.Content.ReadAsStringAsync(); response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); + _token = JsonSerializer.Deserialize(responseContent, OcelotSerializerOptions.Web); var configPath = $"{adminPath}/.well-known/openid-configuration"; response = await _httpClient.GetAsync(configPath); response.EnsureSuccessStatusCode(); @@ -825,7 +825,7 @@ private static async Task GivenThereIsAConfiguration(FileConfiguration fileConfi { var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.Web); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.IntegrationTests/BearerToken.cs b/test/Ocelot.IntegrationTests/BearerToken.cs index ffc3688eb..10fc31575 100644 --- a/test/Ocelot.IntegrationTests/BearerToken.cs +++ b/test/Ocelot.IntegrationTests/BearerToken.cs @@ -1,15 +1,15 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Ocelot.IntegrationTests; internal class BearerToken { - [JsonProperty("access_token")] + [JsonPropertyName("access_token")] public string AccessToken { get; set; } - [JsonProperty("expires_in")] + [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } - [JsonProperty("token_type")] + [JsonPropertyName("token_type")] public string TokenType { get; set; } } diff --git a/test/Ocelot.IntegrationTests/CacheManagerTests.cs b/test/Ocelot.IntegrationTests/CacheManagerTests.cs index 9682de44a..9f388806d 100644 --- a/test/Ocelot.IntegrationTests/CacheManagerTests.cs +++ b/test/Ocelot.IntegrationTests/CacheManagerTests.cs @@ -3,14 +3,15 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Newtonsoft.Json; using Ocelot.Administration; using Ocelot.Cache.CacheManager; using Ocelot.Configuration.File; -using Ocelot.DependencyInjection; +using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Middleware; using System.Net; -using System.Net.Http.Headers; +using System.Net.Http.Headers; +using System.Text.Json; namespace Ocelot.IntegrationTests; @@ -111,7 +112,7 @@ private async Task GivenIHaveAnOcelotToken(string adminPath) var response = await _httpClient.PostAsync(tokenUrl, content); var responseContent = await response.Content.ReadAsStringAsync(); response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); + _token = JsonSerializer.Deserialize(responseContent, OcelotSerializerOptions.Web); var configPath = $"{adminPath}/.well-known/openid-configuration"; response = await _httpClient.GetAsync(configPath); response.EnsureSuccessStatusCode(); @@ -161,7 +162,7 @@ private static void GivenThereIsAConfiguration(FileConfiguration fileConfigurati // TODO: Turn method as async var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.Web); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.IntegrationTests/HeaderTests.cs b/test/Ocelot.IntegrationTests/HeaderTests.cs index 04ea60323..bb5d65e84 100644 --- a/test/Ocelot.IntegrationTests/HeaderTests.cs +++ b/test/Ocelot.IntegrationTests/HeaderTests.cs @@ -2,11 +2,12 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; using Ocelot.Configuration.File; -using Ocelot.DependencyInjection; +using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Middleware; -using System.Net; +using System.Net; +using System.Text.Json; [assembly: CollectionBehavior(DisableTestParallelization = true)] @@ -122,7 +123,7 @@ private static void GivenThereIsAConfiguration(FileConfiguration fileConfigurati { var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.Web); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index bce5761f8..bd6974012 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -2,11 +2,12 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using Ocelot.Middleware; using System.Collections.Concurrent; +using System.Text.Json; namespace Ocelot.IntegrationTests; @@ -118,7 +119,7 @@ private static void GivenThereIsAConfiguration(FileConfiguration fileConfigurati { var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.Web); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.ManualTest/CustomOcelotMiddleware.cs b/test/Ocelot.ManualTest/CustomOcelotMiddleware.cs index f3d251887..d8654fb62 100644 --- a/test/Ocelot.ManualTest/CustomOcelotMiddleware.cs +++ b/test/Ocelot.ManualTest/CustomOcelotMiddleware.cs @@ -1,4 +1,5 @@ -using Ocelot.Logging; +using Ocelot.Infrastructure; +using Ocelot.Logging; using Ocelot.Middleware; using System.Text.Json; using System.Threading.Tasks; @@ -16,7 +17,7 @@ public static Task Invoke(HttpContext context, Func next) { logger.LogInformation(() => { - var metadataInJson = JsonSerializer.Serialize(metadata); + var metadataInJson = JsonSerializer.Serialize(metadata, OcelotSerializerOptions.Web); var message = $"My custom middleware found some metadata: {metadataInJson}"; return message; }); diff --git a/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs index 5a37ed05e..ef5e444ba 100644 --- a/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Hosting; -using Newtonsoft.Json; using Ocelot.Configuration.ChangeTracking; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; -using Ocelot.DependencyInjection; -using System.Runtime.CompilerServices; +using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; +using System.Runtime.CompilerServices; +using System.Text.Json; namespace Ocelot.UnitTests.Configuration; @@ -156,14 +157,14 @@ private void ThenTheConfigurationIsStoredAs(FileConfiguration expecteds) private static void ThenTheOcelotJsonIsStoredAs(FileInfo ocelotJson, FileConfiguration expecteds) { var actual = File.ReadAllText(ocelotJson.FullName); - var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); + var expectedText = JsonSerializer.Serialize(expecteds, OcelotSerializerOptions.WebWriteIndented); actual.ShouldBe(expectedText); } private void GivenTheConfigurationIs(FileConfiguration fileConfiguration, [CallerMemberName] string environmentName = null) { - var environmentSpecificPath = Path.Combine(TestID, string.Format(ConfigurationBuilderExtensions.EnvironmentConfigFile, environmentName)); - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); + var environmentSpecificPath = Path.Combine(TestID, string.Format(ConfigurationBuilderExtensions.EnvironmentConfigFile, environmentName)); + var jsonConfiguration = JsonSerializer.Serialize(fileConfiguration, OcelotSerializerOptions.WebWriteIndented); var environmentSpecific = new FileInfo(environmentSpecificPath); if (environmentSpecific.Exists) { @@ -178,7 +179,7 @@ private void ThenTheConfigurationJsonIsIndented(FileConfiguration expecteds, [Ca { var environmentSpecific = Path.Combine(TestID, string.Format(ConfigurationBuilderExtensions.EnvironmentConfigFile, environmentName)); var actual = File.ReadAllText(environmentSpecific); - var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); + var expectedText = JsonSerializer.Serialize(expecteds, OcelotSerializerOptions.WebWriteIndented); actual.ShouldBe(expectedText); _files.Add(environmentSpecific); } diff --git a/test/Ocelot.UnitTests/Consul/ConsulFileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Consul/ConsulFileConfigurationRepositoryTests.cs index 474c5c1ac..d10b8cdcf 100644 --- a/test/Ocelot.UnitTests/Consul/ConsulFileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Consul/ConsulFileConfigurationRepositoryTests.cs @@ -1,13 +1,14 @@ using Consul; using Microsoft.Extensions.Options; -using Newtonsoft.Json; using Ocelot.Cache; -using Ocelot.Configuration.File; +using Ocelot.Configuration.File; +using Ocelot.Infrastructure; using Ocelot.Logging; using Ocelot.Provider.Consul; using Ocelot.Provider.Consul.Interfaces; using Ocelot.Responses; -using System.Text; +using System.Text; +using System.Text.Json; namespace Ocelot.UnitTests.Consul; @@ -141,8 +142,8 @@ private void GivenTheConfigKeyComesFromFileConfig(string key) private void ThenTheConfigurationIs(FileConfiguration config) { - var expected = JsonConvert.SerializeObject(config, Formatting.Indented); - var result = JsonConvert.SerializeObject(_getResult.Data, Formatting.Indented); + var expected = JsonSerializer.Serialize(config, OcelotSerializerOptions.WebWriteIndented); + var result = JsonSerializer.Serialize(_getResult.Data, OcelotSerializerOptions.WebWriteIndented); result.ShouldBe(expected); } @@ -169,7 +170,7 @@ private void GivenFetchFromConsulReturnsNull() private void GivenFetchFromConsulSucceeds() { - var json = JsonConvert.SerializeObject(_fileConfiguration, Formatting.Indented); + var json = JsonSerializer.Serialize(_fileConfiguration, OcelotSerializerOptions.WebWriteIndented); var bytes = Encoding.UTF8.GetBytes(json); var kvp = new KVPair("OcelotConfiguration") { @@ -185,7 +186,7 @@ private void GivenFetchFromConsulSucceeds() private void ThenTheConfigurationIsStoredAs(FileConfiguration config) { - var json = JsonConvert.SerializeObject(config, Formatting.Indented); + var json = JsonSerializer.Serialize(config, OcelotSerializerOptions.WebWriteIndented); var bytes = Encoding.UTF8.GetBytes(json); _kvEndpoint.Verify(x => x.Put(It.Is(k => k.Value.SequenceEqual(bytes)), It.IsAny()), Times.Once); } diff --git a/test/Ocelot.UnitTests/Consul/ConsulTests.cs b/test/Ocelot.UnitTests/Consul/ConsulTests.cs index 13f39129a..7fb6ff18b 100644 --- a/test/Ocelot.UnitTests/Consul/ConsulTests.cs +++ b/test/Ocelot.UnitTests/Consul/ConsulTests.cs @@ -2,11 +2,12 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; +using Ocelot.Infrastructure; using Ocelot.Logging; using Ocelot.Provider.Consul; using Ocelot.Provider.Consul.Interfaces; using System.Runtime.CompilerServices; +using System.Text.Json; using ConsulProvider = Ocelot.Provider.Consul.Consul; namespace Ocelot.UnitTests.Consul; @@ -204,7 +205,7 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider([CallerMemberName] _receivedToken = values.First(); } - var json = JsonConvert.SerializeObject(_consulServiceEntries); + var json = JsonSerializer.Serialize(_consulServiceEntries, OcelotSerializerOptions.Web); context.Response.Headers.Append("Content-Type", "application/json"); await context.Response.WriteAsync(json); } diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 3002048e7..dc0b87bc5 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.DependencyInjection; +using Ocelot.Infrastructure; using System.Runtime.CompilerServices; +using System.Text.Json; namespace Ocelot.UnitTests.DependencyInjection; @@ -187,8 +188,8 @@ private void GivenMultipleConfigurationFiles(string folder, bool withEnvironment foreach (var part in configParts) { - var filename = Path.Combine(folder, string.Format(ConfigurationBuilderExtensions.EnvironmentConfigFile, part.Key)); - File.WriteAllText(filename, JsonConvert.SerializeObject(part.Value, Formatting.Indented)); + var filename = Path.Combine(folder, string.Format(ConfigurationBuilderExtensions.EnvironmentConfigFile, part.Key)); + File.WriteAllText(filename, JsonSerializer.Serialize(part.Value, OcelotSerializerOptions.WebWriteIndented)); _files.Add(filename); } } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 736282208..7fc9fa8dc 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -324,17 +323,11 @@ private void CstorShouldUseDefaultBuilderToInitMvcCoreBuilder() .ShouldNotBeNull() .GetType().Name.ShouldBe("AuthorizationApplicationModelProvider"); - // .AddNewtonsoftJson() - _serviceProvider.GetServices>() - .FirstOrDefault(s => s.GetType().Name == "NewtonsoftJsonMvcOptionsSetup") + // use system text json + _serviceProvider.GetServices>() + .FirstOrDefault(s => s.GetType().Name == "SystemTextJsonResultExecutor") .ShouldNotBeNull(); - _serviceProvider.GetService>() - .ShouldNotBeNull() - .GetType().Name.ShouldBe("NewtonsoftJsonResultExecutor"); - _serviceProvider.GetService() - .ShouldNotBeNull() - .GetType().Name.ShouldBe("NewtonsoftJsonHelper"); - } + } [Fact] public void Should_use_custom_mvc_builder_no_configuration() diff --git a/test/Ocelot.UnitTests/Kubernetes/KubeTests.cs b/test/Ocelot.UnitTests/Kubernetes/KubeTests.cs index d6c13f60d..1ee689e82 100644 --- a/test/Ocelot.UnitTests/Kubernetes/KubeTests.cs +++ b/test/Ocelot.UnitTests/Kubernetes/KubeTests.cs @@ -3,13 +3,14 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; +using Ocelot.Infrastructure; using Ocelot.Logging; using Ocelot.Provider.Kubernetes; using Ocelot.Provider.Kubernetes.Interfaces; using Ocelot.Testing; using Ocelot.Values; using System.Runtime.CompilerServices; +using System.Text.Json; namespace Ocelot.UnitTests.Kubernetes; @@ -160,7 +161,7 @@ Task ProcessKubernetesRequest(HttpContext context) token = values.First(); } - var json = JsonConvert.SerializeObject(endpointEntries); + var json = JsonSerializer.Serialize(endpointEntries, OcelotSerializerOptions.Web); context.Response.Headers.Append("Content-Type", "application/json"); return context.Response.WriteAsync(json); } diff --git a/test/Ocelot.UnitTests/OcelotSerializerOptionsTests.cs b/test/Ocelot.UnitTests/OcelotSerializerOptionsTests.cs new file mode 100644 index 000000000..a488393bc --- /dev/null +++ b/test/Ocelot.UnitTests/OcelotSerializerOptionsTests.cs @@ -0,0 +1,70 @@ +using Ocelot.Infrastructure; +using System.Text.Json; + +namespace Ocelot.UnitTests; + +public class OcelotSerializerOptionsTests : UnitTest +{ + [Fact] + public void should_json_path() + { + //Arrange + var json = "{\"id\":1,\"writerId\":1,\"postId\":2,\"text\":\"text1\"}"; + var path = "$writerId"; + var document = JsonDocument.Parse(json); + + //Act + var result = document.ExtractValuesFromJsonPath(path); + + //Assert + result.ShouldBeEquivalentTo(new List { "1" }); + } + + [Fact] + public void should_json_path_array() + { + //Arrange + var json = + "[{\"id\":1,\"writerId\":1,\"postId\":2,\"text\":\"text1\"},{\"id\":2,\"writerId\":1,\"postId\":2,\"text\":\"text2\"}]"; + var path = "$[*].writerId"; + var document = JsonDocument.Parse(json); + + //Act + var result = document.ExtractValuesFromJsonPath(path); + + //Assert + result.ShouldBeEquivalentTo(new List { "1", "1" }); + } + + [Fact] + public void should_json_path_nested() + { + //Arrange + var json = + "[{\"id\":1,\"writerId\":1,\"postId\":2,\"text\":\"text1\",\"comments\":[{\"commentId\":1,\"text\":\"Good post!\"},{\"commentId\":2,\"text\":\"Nice post!\"}]},{\"id\":2,\"writerId\":2,\"postId\":2,\"text\":\"text2\",\"comments\":[{\"commentId\":3,\"text\":\"Interesting.\"}]}]"; + var path = "$[*].comments[*].text"; + var document = JsonDocument.Parse(json); + + //Act + var result = document.ExtractValuesFromJsonPath(path); + + //Assert + result.ShouldBeEquivalentTo(new List { "Good post!", "Nice post!", "Interesting." }); + } + + [Fact] + public void should_json_path_nested_null() + { + //Arrange + var json = + "[{\"id\":1,\"writerId\":1,\"postId\":2,\"text\":\"text1\",\"comments\":[{\"commentId\":1,\"text\":null},{\"commentId\":2,\"text\":\"Nice post!\"}]},{\"id\":2,\"writerId\":2,\"postId\":2,\"text\":\"text2\",\"comments\":[{\"commentId\":3,\"text\":\"Interesting.\"}]}]"; + var path = "$[*].comments[*].text"; + var document = JsonDocument.Parse(json); + + //Act + var result = document.ExtractValuesFromJsonPath(path); + + //Assert + result.ShouldBeEquivalentTo(new List { "Nice post!", "Interesting." }); + } +}