diff --git a/src/Paralax.CQRS.Queries/src/Paralax.CQRS.Queries/Dispatchers/QueryDispatcher.cs b/src/Paralax.CQRS.Queries/src/Paralax.CQRS.Queries/Dispatchers/QueryDispatcher.cs index 890d1f4..74c7cc6 100644 --- a/src/Paralax.CQRS.Queries/src/Paralax.CQRS.Queries/Dispatchers/QueryDispatcher.cs +++ b/src/Paralax.CQRS.Queries/src/Paralax.CQRS.Queries/Dispatchers/QueryDispatcher.cs @@ -19,22 +19,25 @@ public async Task QueryAsync(IQuery query, Cancellati using var scope = _serviceProvider.CreateScope(); var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult)); var handler = scope.ServiceProvider.GetRequiredService(handlerType); - - var handleAsyncMethod = handlerType.GetMethod(nameof(IQueryHandler, TResult>.HandleAsync)); - - if (handleAsyncMethod == null) - { - throw new InvalidOperationException($"Handler for query '{query.GetType().Name}' does not contain a valid 'HandleAsync' method."); - } - - var resultTask = (Task?)handleAsyncMethod.Invoke(handler, new object[] { query, cancellationToken }); - if (resultTask == null) - { - throw new InvalidOperationException($"HandleAsync method for '{query.GetType().Name}' returned null."); - } - - return await resultTask; + // We get the 'HandleAsync' method directly from the handler + var handleAsyncMethod = handlerType.GetMethod(nameof(IQueryHandler, TResult>.HandleAsync)); + // if (handleAsyncMethod == null) + // { + // throw new InvalidOperationException($"Handler for query '{query.GetType().Name}' does not contain a valid 'HandleAsync' method."); + // } + + // var taskResult = handleAsyncMethod.Invoke(handler, new object[] { query, cancellationToken }); + + // if (taskResult is Task resultTask) + // { + // return await resultTask; + // } + + // throw new InvalidOperationException($"HandleAsync method for '{query.GetType().Name}' returned an invalid result."); + return await (Task) handlerType + .GetMethod(nameof(IQueryHandler, TResult>.HandleAsync))? + .Invoke(handler, new object[] {query, cancellationToken}); } public async Task QueryAsync(TQuery query, CancellationToken cancellationToken = default) diff --git a/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Builders/DispatcherEndpointsBuilder.cs b/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Builders/DispatcherEndpointsBuilder.cs index b2fbde1..8c6a014 100644 --- a/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Builders/DispatcherEndpointsBuilder.cs +++ b/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Builders/DispatcherEndpointsBuilder.cs @@ -6,8 +6,10 @@ using NetJSON; using Paralax.CQRS.Commands; using Paralax.CQRS.Queries; +using Paralax.WebApi; +using Paralax.CQRS.WebApi; -namespace Paralax.WebApi.CQRS.Builders +namespace Paralax.CQRS.WebApi.Builders { public class DispatcherEndpointsBuilder : IDispatcherEndpointsBuilder { @@ -49,7 +51,8 @@ public IDispatcherEndpointsBuilder Get(string path, return; } - await WriteJsonAsync(ctx.Response, result); + // await WriteJsonAsync(ctx.Response, result); + await ctx.Response.WriteJsonAsync(result); return; } @@ -137,11 +140,17 @@ private static async Task BuildCommandContext(T command, HttpContext context, private static async Task WriteJsonAsync(HttpResponse response, object result) { + NetJSON.NetJSON.DateFormat = NetJSON.NetJSONDateFormat.ISO; + NetJSON.NetJSON.SkipDefaultValue = false; + response.ContentType = "application/json"; + var json = NetJSON.NetJSON.Serialize(result); + await response.WriteAsync(json); } + private static async Task ReadJsonAsync(HttpContext context) where T : class { var body = await new StreamReader(context.Request.Body).ReadToEndAsync(); diff --git a/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Extensions.cs b/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Extensions.cs index 7e513b5..bd17d54 100644 --- a/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Extensions.cs +++ b/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Extensions.cs @@ -5,10 +5,10 @@ using Microsoft.Extensions.DependencyInjection; using Paralax.CQRS.Commands; using Paralax.CQRS.Queries; +using Paralax.CQRS.WebApi.Builders; +using Paralax.CQRS.WebApi.Middlewares; using Paralax.WebApi; -using Paralax.WebApi.CQRS; -using Paralax.WebApi.CQRS.Builders; -using Paralax.WebApi.CQRS.Middlewares; + namespace Paralax.CQRS.WebApi { diff --git a/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/IDispatcherEndpointsBuilder.cs b/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/IDispatcherEndpointsBuilder.cs index 2f8a64e..d4d25cd 100644 --- a/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/IDispatcherEndpointsBuilder.cs +++ b/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/IDispatcherEndpointsBuilder.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -namespace Paralax.WebApi.CQRS +namespace Paralax.CQRS.WebApi { public interface IDispatcherEndpointsBuilder { diff --git a/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Middlewares/PublicContractsMiddleware.cs b/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Middlewares/PublicContractsMiddleware.cs index 0f54aa9..14efd5d 100644 --- a/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Middlewares/PublicContractsMiddleware.cs +++ b/src/Paralax.CQRS.WebApi/src/Paralax.CQRS.WebApi/Middlewares/PublicContractsMiddleware.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; using System.Reflection; -namespace Paralax.WebApi.CQRS.Middlewares +namespace Paralax.CQRS.WebApi.Middlewares { public class PublicContractsMiddleware { diff --git a/src/Paralax.HTTP/src/Paralax.HTTP/MessagePackHttpClientSerializer.cs b/src/Paralax.HTTP/src/Paralax.HTTP/MessagePackHttpClientSerializer.cs new file mode 100644 index 0000000..d075ca1 --- /dev/null +++ b/src/Paralax.HTTP/src/Paralax.HTTP/MessagePackHttpClientSerializer.cs @@ -0,0 +1,42 @@ +using System.IO; +using System.Threading.Tasks; +using MessagePack; +using MessagePack.Resolvers; + +namespace Paralax.HTTP +{ + public class MessagePackHttpClientSerializer : IHttpClientSerializer + { + private readonly MessagePackSerializerOptions _options; + + public MessagePackHttpClientSerializer(MessagePackSerializerOptions options = null) + { + _options = options ?? MessagePackSerializerOptions.Standard + .WithResolver(ContractlessStandardResolver.Instance) + .WithCompression(MessagePackCompression.Lz4BlockArray) + .WithSecurity(MessagePackSecurity.UntrustedData); + } + + public string Serialize(T value) + { + var bytes = MessagePackSerializer.Serialize(value, _options); + return System.Convert.ToBase64String(bytes); + } + + public async Task SerializeAsync(Stream stream, T value) + { + await MessagePackSerializer.SerializeAsync(stream, value, _options); + } + + public async ValueTask DeserializeAsync(Stream stream) + { + return await MessagePackSerializer.DeserializeAsync(stream, _options); + } + + public T Deserialize(string base64) + { + var bytes = System.Convert.FromBase64String(base64); + return MessagePackSerializer.Deserialize(bytes, _options); + } + } +} diff --git a/src/Paralax.HTTP/src/Paralax.HTTP/NetJsonHttpClientSerializer.cs b/src/Paralax.HTTP/src/Paralax.HTTP/NetJsonHttpClientSerializer.cs index 60c7c89..85d2f49 100644 --- a/src/Paralax.HTTP/src/Paralax.HTTP/NetJsonHttpClientSerializer.cs +++ b/src/Paralax.HTTP/src/Paralax.HTTP/NetJsonHttpClientSerializer.cs @@ -12,19 +12,26 @@ public NetJsonHttpClientSerializer(NetJSONSettings settings = null) { _settings = settings ?? new NetJSONSettings { - UseEnumString = true, - CaseSensitive = false, - SkipDefaultValue = true + UseEnumString = true, + CaseSensitive = false, + SkipDefaultValue = false, + DateFormat = NetJSONDateFormat.ISO, + TimeZoneFormat = NetJSONTimeZoneFormat.Utc }; } - public string Serialize(T value) => NetJSON.NetJSON.Serialize(value, _settings); + public string Serialize(T value) + { + return NetJSON.NetJSON.Serialize(value, _settings); + } public async Task SerializeAsync(Stream stream, T value) { - using (var writer = new StreamWriter(stream)) + var json = NetJSON.NetJSON.Serialize(value, _settings); + + using (var writer = new StreamWriter(stream)) { - await writer.WriteAsync(NetJSON.NetJSON.Serialize(value, _settings)); + await writer.WriteAsync(json); await writer.FlushAsync(); } } @@ -34,10 +41,16 @@ public async ValueTask DeserializeAsync(Stream stream) using (var reader = new StreamReader(stream)) { var content = await reader.ReadToEndAsync(); - return NetJSON.NetJSON.Deserialize(content, _settings); + + var result = NetJSON.NetJSON.Deserialize(content, _settings); + + return result; } } - public T Deserialize(string json) => NetJSON.NetJSON.Deserialize(json, _settings); + public T Deserialize(string json) + { + return NetJSON.NetJSON.Deserialize(json, _settings); + } } } diff --git a/src/Paralax.HTTP/src/Paralax.HTTP/Paralax.HTTP.csproj b/src/Paralax.HTTP/src/Paralax.HTTP/Paralax.HTTP.csproj index 48c6e02..cc85ffb 100644 --- a/src/Paralax.HTTP/src/Paralax.HTTP/Paralax.HTTP.csproj +++ b/src/Paralax.HTTP/src/Paralax.HTTP/Paralax.HTTP.csproj @@ -35,6 +35,8 @@ \ + + diff --git a/src/Paralax.HTTP/src/Paralax.HTTP/ParalaxHttpClient.cs b/src/Paralax.HTTP/src/Paralax.HTTP/ParalaxHttpClient.cs index ad3e309..22baec7 100644 --- a/src/Paralax.HTTP/src/Paralax.HTTP/ParalaxHttpClient.cs +++ b/src/Paralax.HTTP/src/Paralax.HTTP/ParalaxHttpClient.cs @@ -180,6 +180,7 @@ private async Task> CreateHttpResult(HttpResponseMessage respon var stream = await response.Content.ReadAsStreamAsync(); serializer ??= _serializer; var result = await serializer.DeserializeAsync(stream); + Console.WriteLine($"Deserialized Result: {result}"); return new HttpResult(result, response); } @@ -198,7 +199,6 @@ private StringContent GetJsonPayload(object data, IHttpClientSerializer serializ return content; } - // Retry policy using Polly private async Task RetryPolicy(Func> action) { return await Policy @@ -209,22 +209,18 @@ private async Task RetryPolicy(Func> action) protected virtual async Task> SendResultAsync(string uri, Method method, HttpContent content = null, IHttpClientSerializer serializer = null) { - // Sending the request using the method and content var response = await SendAsync(uri, method, content); - // If the response status code is not successful, return a default result if (!response.IsSuccessStatusCode) { return new HttpResult(default, response); } - // Read the response content stream var stream = await response.Content.ReadAsStreamAsync(); - // Deserialize the stream using the provided serializer (or default to _serializer) var result = await DeserializeJsonFromStream(stream, serializer); + Console.WriteLine($"Deserialized Result: {result}"); - // Return the deserialized result along with the response return new HttpResult(result, response); } @@ -235,7 +231,6 @@ private async Task DeserializeJsonFromStream(Stream stream, IHttpClientSer return default; } - // Use the provided serializer or default to _serializer serializer ??= _serializer; return await serializer.DeserializeAsync(stream); @@ -253,7 +248,6 @@ public enum Method } } - // Extension method for converting Method enum to HttpMethod internal static class MethodExtensions { public static HttpMethod ToHttpMethod(this ParalaxHttpClient.Method method) diff --git a/src/Paralax.WebApi/src/Paralax.WebApi/Exceptions/ErrorHandlerMiddleware.cs b/src/Paralax.WebApi/src/Paralax.WebApi/Exceptions/ErrorHandlerMiddleware.cs index 16ee9b2..7e3603a 100644 --- a/src/Paralax.WebApi/src/Paralax.WebApi/Exceptions/ErrorHandlerMiddleware.cs +++ b/src/Paralax.WebApi/src/Paralax.WebApi/Exceptions/ErrorHandlerMiddleware.cs @@ -3,19 +3,21 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using NetJSON; +using Open.Serialization.Json; namespace Paralax.WebApi.Exceptions { internal sealed class ErrorHandlerMiddleware : IMiddleware { private readonly IExceptionToResponseMapper _exceptionToResponseMapper; + private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; public ErrorHandlerMiddleware(IExceptionToResponseMapper exceptionToResponseMapper, - ILogger logger) + IJsonSerializer jsonSerializer, ILogger logger) { _exceptionToResponseMapper = exceptionToResponseMapper; + _jsonSerializer = jsonSerializer; _logger = logger; } @@ -28,7 +30,7 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) catch (Exception exception) { _logger.LogError(exception, "An error occurred while processing the request."); - await HandleErrorAsync(context, exception); // Handle the error when caught + await HandleErrorAsync(context, exception); } } @@ -48,10 +50,7 @@ private async Task HandleErrorAsync(HttpContext context, Exception exception) context.Response.ContentType = "application/json"; - // Use NetJSON to serialize the response - var jsonResponse = NetJSON.NetJSON.Serialize(response); - - await context.Response.WriteAsync(jsonResponse); + await _jsonSerializer.SerializeAsync(context.Response.Body, response); } } } diff --git a/src/Paralax.WebApi/src/Paralax.WebApi/Extensions.cs b/src/Paralax.WebApi/src/Paralax.WebApi/Extensions.cs index d944726..395a8e8 100644 --- a/src/Paralax.WebApi/src/Paralax.WebApi/Extensions.cs +++ b/src/Paralax.WebApi/src/Paralax.WebApi/Extensions.cs @@ -7,6 +7,8 @@ using System.Net; using System.Reflection; using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using System.Web; using Microsoft.AspNetCore.Builder; @@ -15,7 +17,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using NetJSON; +using Open.Serialization.Json; using Paralax.WebApi.Exceptions; using Paralax.WebApi.Formatters; using Paralax.WebApi.Requests; @@ -57,7 +59,7 @@ public static Task DispatchAsync(this HttpContext co return handler.HandleAsync(request); } - public static IParalaxBuilder AddWebApi(this IParalaxBuilder builder, Action configureMvc = null, string sectionName = SectionName) + public static IParalaxBuilder AddWebApi(this IParalaxBuilder builder, Action configureMvc = null, IJsonSerializer jsonSerializer = null, string sectionName = SectionName) { if (string.IsNullOrWhiteSpace(sectionName)) { @@ -69,23 +71,41 @@ public static IParalaxBuilder AddWebApi(this IParalaxBuilder builder, Action(); builder.Services.AddSingleton(new WebApiEndpointDefinitions()); var options = builder.GetOptions(sectionName); builder.Services.AddSingleton(options); + _bindRequestFromRoute = options.BindRequestFromRoute; var mvcCoreBuilder = builder.Services .AddLogging() .AddMvcCore(); + // Configure formatters using the injected IJsonSerializer mvcCoreBuilder.AddMvcOptions(o => { o.OutputFormatters.Clear(); - o.OutputFormatters.Add(new JsonOutputFormatter()); + o.OutputFormatters.Add(new JsonOutputFormatter(jsonSerializer)); // Use the new serializer o.InputFormatters.Clear(); - o.InputFormatters.Add(new JsonInputFormatter()); + o.InputFormatters.Add(new JsonInputFormatter(jsonSerializer)); // Use the new serializer }) .AddDataAnnotations() .AddApiExplorer() @@ -122,9 +142,8 @@ public static async Task ReadJsonAsync(this HttpContext context) try { - using var reader = new StreamReader(context.Request.Body); - var json = await reader.ReadToEndAsync(); - var payload = NetJSON.NetJSON.Deserialize(json); + var serializer = context.RequestServices.GetRequiredService(); + var payload = await serializer.DeserializeAsync(context.Request.Body); if (_bindRequestFromRoute && HasRouteData(context.Request)) { @@ -159,7 +178,6 @@ public static IParalaxBuilder AddErrorHandler(this IParalaxBuilder builder) return builder; } - public static T ReadQuery(this HttpContext context) where T : class { var request = context.Request; @@ -183,25 +201,27 @@ public static T ReadQuery(this HttpContext context) where T : class { if (!string.IsNullOrEmpty(key)) { - values[key] = queryString[key]; + values.TryAdd(key, queryString[key]); } } } - // If there are no values, return a new instance of the object + var serializer = context.RequestServices.GetRequiredService(); if (!values.Any()) { - return null; + return serializer.Deserialize(EmptyJsonObject); } - // Serialize the dictionary of values into JSON - var serialized = NetJSON.NetJSON.Serialize(values); + var serialized = serializer.Serialize(values.ToDictionary(k => k.Key, k => k.Value)) + ?.Replace("\\\"", "\"") + .Replace("\"{", "{") + .Replace("}\"", "}") + .Replace("\"[", "[") + .Replace("]\"", "]"); - // Deserialize the JSON back into the expected object type - return NetJSON.NetJSON.Deserialize(serialized); + return serializer.Deserialize(serialized); } - public static Task Ok(this HttpResponse response, object data = null) { response.StatusCode = (int)HttpStatusCode.OK; @@ -211,11 +231,16 @@ public static Task Ok(this HttpResponse response, object data = null) public static Task Created(this HttpResponse response, string location = null, object data = null) { response.StatusCode = (int)HttpStatusCode.Created; - if (!string.IsNullOrWhiteSpace(location) && !response.Headers.ContainsKey(LocationHeader)) + if (string.IsNullOrWhiteSpace(location)) + { + return Task.CompletedTask; + } + + if (!response.Headers.ContainsKey(LocationHeader)) { response.Headers.Add(LocationHeader, location); } - return data != null ? response.WriteJsonAsync(data) : Task.CompletedTask; + return data is null ? Task.CompletedTask : response.WriteJsonAsync(data); } public static Task Accepted(this HttpResponse response) @@ -261,11 +286,20 @@ public static Task InternalServerError(this HttpResponse response) } public static async Task WriteJsonAsync(this HttpResponse response, T value) - { - response.ContentType = JsonContentType; - var json = NetJSON.NetJSON.Serialize(value); - await response.WriteAsync(json); - } +{ + response.ContentType = JsonContentType; + var serializer = response.HttpContext.RequestServices.GetRequiredService(); + + // Serialize the object to a string + var serializedJson = serializer.Serialize(value); + + // Log the serialized JSON to the console + Console.WriteLine($"Serialized JSON Response: {serializedJson}"); + + // Write the serialized JSON to the response body + await response.WriteAsync(serializedJson); +} + public static T Bind(this T model, Expression> expression, object value) => model.Bind(expression, value); diff --git a/src/Paralax.WebApi/src/Paralax.WebApi/Formatters/JsonInputFormatter.cs b/src/Paralax.WebApi/src/Paralax.WebApi/Formatters/JsonInputFormatter.cs index c3d4530..291ee0b 100644 --- a/src/Paralax.WebApi/src/Paralax.WebApi/Formatters/JsonInputFormatter.cs +++ b/src/Paralax.WebApi/src/Paralax.WebApi/Formatters/JsonInputFormatter.cs @@ -5,7 +5,7 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Formatters; -using NetJSON; +using Open.Serialization.Json; namespace Paralax.WebApi.Formatters { @@ -13,15 +13,15 @@ internal class JsonInputFormatter : IInputFormatter { private const string EmptyJson = "{}"; private readonly ConcurrentDictionary _methods = new(); + private readonly IJsonSerializer _serializer; private readonly MethodInfo _deserializeMethod; - public JsonInputFormatter() + public JsonInputFormatter(IJsonSerializer serializer) { - // Ensure that the correct Deserialize method is selected - _deserializeMethod = typeof(NetJSON.NetJSON).GetMethods() - .Single(m => m.IsGenericMethod && m.Name == nameof(NetJSON.NetJSON.Deserialize) - && m.GetParameters().Length == 1 - && m.GetParameters()[0].ParameterType == typeof(string)); // Ensure it's the correct overload + _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + + _deserializeMethod = _serializer.GetType().GetMethods() + .Single(m => m.IsGenericMethod && m.Name == nameof(_serializer.Deserialize)); } public bool CanRead(InputFormatterContext context) @@ -52,14 +52,12 @@ public async Task ReadAsync(InputFormatterContext context) try { - // Use NetJSON to deserialize the JSON string - var result = method.Invoke(null, new object[] { json }); + var result = method.Invoke(_serializer, new object[] { json }); return await InputFormatterResult.SuccessAsync(result); } catch (Exception ex) { - // Handle deserialization errors throw new InvalidOperationException("Invalid JSON format", ex); } } diff --git a/src/Paralax.WebApi/src/Paralax.WebApi/Formatters/JsonOutputFormatter.cs b/src/Paralax.WebApi/src/Paralax.WebApi/Formatters/JsonOutputFormatter.cs index cdb6ca3..b932434 100644 --- a/src/Paralax.WebApi/src/Paralax.WebApi/Formatters/JsonOutputFormatter.cs +++ b/src/Paralax.WebApi/src/Paralax.WebApi/Formatters/JsonOutputFormatter.cs @@ -1,12 +1,19 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; -using NetJSON; +using Open.Serialization.Json; namespace Paralax.WebApi.Formatters { internal class JsonOutputFormatter : IOutputFormatter { + private readonly IJsonSerializer _serializer; + + public JsonOutputFormatter(IJsonSerializer serializer) + { + _serializer = serializer; + } + public bool CanWriteResult(OutputFormatterCanWriteContext context) { return true; @@ -27,8 +34,7 @@ public async Task WriteAsync(OutputFormatterWriteContext context) return; } - var serializedJson = NetJSON.NetJSON.Serialize(context.Object); - await context.HttpContext.Response.WriteAsync(serializedJson); + await _serializer.SerializeAsync(context.HttpContext.Response.Body, context.Object); } } } diff --git a/src/Paralax.WebApi/src/Paralax.WebApi/Paralax.WebApi.csproj b/src/Paralax.WebApi/src/Paralax.WebApi/Paralax.WebApi.csproj index 2490545..4f81186 100644 --- a/src/Paralax.WebApi/src/Paralax.WebApi/Paralax.WebApi.csproj +++ b/src/Paralax.WebApi/src/Paralax.WebApi/Paralax.WebApi.csproj @@ -38,6 +38,8 @@ + + diff --git a/src/Paralax.WebApi/src/Paralax.WebApi/ParalaxFormatterResolver.cs b/src/Paralax.WebApi/src/Paralax.WebApi/ParalaxFormatterResolver.cs index c5dbb7b..0b085ae 100644 --- a/src/Paralax.WebApi/src/Paralax.WebApi/ParalaxFormatterResolver.cs +++ b/src/Paralax.WebApi/src/Paralax.WebApi/ParalaxFormatterResolver.cs @@ -10,7 +10,6 @@ internal sealed class ParalaxFormatterResolver : IJsonFormatterResolver { public static readonly IJsonFormatterResolver Instance = new ParalaxFormatterResolver(); - // Resolvers that will be used if no custom formatter is available. private static readonly IJsonFormatterResolver[] Resolvers = { StandardResolver.CamelCase, // Use CamelCase as a standard fallback resolver diff --git a/src/Paralax.WebApi/src/Paralax.WebApi/Parsers/JsonParser.cs b/src/Paralax.WebApi/src/Paralax.WebApi/Parsers/JsonParser.cs index 59c124e..7e0a7c0 100644 --- a/src/Paralax.WebApi/src/Paralax.WebApi/Parsers/JsonParser.cs +++ b/src/Paralax.WebApi/src/Paralax.WebApi/Parsers/JsonParser.cs @@ -1,79 +1,115 @@ using System; using System.Collections.Generic; -using System.IO; -using NetJSON; +using System.Text.Json; namespace Paralax.WebApi.Parsers { - // This JSON parser uses the NetJSON library for serialization/deserialization. + // JSON parser using System.Text.Json for serialization/deserialization. // It parses a JSON string into key-value pairs suitable for configuration or other dictionary-based representations. public class JsonParser { private readonly Dictionary _data = new(StringComparer.OrdinalIgnoreCase); + private readonly Stack _stack = new(); public IDictionary Parse(string json) { - try + var jsonDocumentOptions = new JsonDocumentOptions { - // Deserialize the JSON into a Dictionary - var deserializedData = NetJSON.NetJSON.Deserialize>(json); - ProcessDictionary(deserializedData, string.Empty); - } - catch (Exception ex) + CommentHandling = JsonCommentHandling.Skip, // Skip comments in JSON + AllowTrailingCommas = true // Allow trailing commas + }; + + // Parse the JSON string using System.Text.Json + using (JsonDocument doc = JsonDocument.Parse(json, jsonDocumentOptions)) { - throw new FormatException($"Failed to parse JSON: {ex.Message}", ex); + if (doc.RootElement.ValueKind != JsonValueKind.Object) + { + throw new FormatException($"Invalid top-level JSON element: {doc.RootElement.ValueKind}"); + } + + // Traverse the JSON structure + VisitElement(doc.RootElement); } return _data; } - private void ProcessDictionary(Dictionary dict, string parentKey) + // Recursively visit each JSON element + private void VisitElement(JsonElement element) { - foreach (var kvp in dict) + var isEmpty = true; + + // Process each property in the JSON object + foreach (JsonProperty property in element.EnumerateObject()) { - var currentKey = string.IsNullOrEmpty(parentKey) ? kvp.Key : $"{parentKey}.{kvp.Key}"; + isEmpty = false; + EnterContext(property.Name); + VisitValue(property.Value); + ExitContext(); + } - if (kvp.Value is Dictionary nestedDict) - { - // Recursively process nested dictionaries - ProcessDictionary(nestedDict, currentKey); - } - else if (kvp.Value is IList list) - { - // Process lists - ProcessList(list, currentKey); - } - else - { - // Add to the data dictionary - if (_data.ContainsKey(currentKey)) - { - throw new FormatException($"Duplicated key: {currentKey}"); - } - _data[currentKey] = kvp.Value?.ToString(); - } + // Handle empty objects + if (isEmpty && _stack.Count > 0) + { + _data[_stack.Peek()] = null; } } - private void ProcessList(IList list, string parentKey) + // Process each value in the JSON object or array + private void VisitValue(JsonElement value) { - for (int i = 0; i < list.Count; i++) + switch (value.ValueKind) { - var currentKey = $"{parentKey}[{i}]"; + case JsonValueKind.Object: + // If it's an object, visit its properties + VisitElement(value); + break; - if (list[i] is Dictionary nestedDict) - { - ProcessDictionary(nestedDict, currentKey); - } - else - { - if (_data.ContainsKey(currentKey)) + case JsonValueKind.Array: + // If it's an array, process each element + int index = 0; + foreach (JsonElement arrayElement in value.EnumerateArray()) { - throw new FormatException($"Duplicated key: {currentKey}"); + EnterContext(index.ToString()); + VisitValue(arrayElement); + ExitContext(); + index++; } - _data[currentKey] = list[i]?.ToString(); - } + break; + + case JsonValueKind.Number: + case JsonValueKind.String: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + var key = _stack.Peek(); + + if (_data.ContainsKey(key)) + { + throw new FormatException($"Duplicated key: {key}"); + } + + // Store the value as a string + _data[key] = value.ToString(); + break; + + default: + throw new FormatException($"Unsupported JSON token: {value.ValueKind}"); } } + + // Enter a new context in the JSON structure (i.e., navigate deeper) + private void EnterContext(string context) + { + _stack.Push(_stack.Count > 0 + ? _stack.Peek() + "." + context // Use '.' to concatenate nested keys + : context); + } + + // Exit the current context (i.e., navigate back up) + private void ExitContext() + { + _stack.Pop(); + } } }