Add TypeJsonConverter and enhance JSON serialization

Introduced a TypeJsonConverter to handle Type serialization and deserialization. Updated dependency injection and helper methods to support customized JSON serializer options, improving flexibility and configurability.
This commit is contained in:
Sipke Schoorstra 2024-10-03 18:50:05 +02:00
parent 86826a89dc
commit 679a0515c8
5 changed files with 73 additions and 16 deletions

View file

@ -0,0 +1,56 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Api.Client.Extensions;
using JetBrains.Annotations;
namespace Elsa.Api.Client.Converters;
/// <summary>
/// Converts <see cref="Type"/> objects to and from their assembly-qualified name.
/// </summary>
[UsedImplicitly]
public class TypeJsonConverter : JsonConverter<Type>
{
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(Type) || typeToConvert.FullName == "System.RuntimeType";
}
/// <inheritdoc />
public override Type? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var typeName = reader.GetString()!;
// Handle collection types.
if (typeName.EndsWith("[]"))
{
var elementTypeName = typeName[..^"[]".Length];
var elementType = Type.GetType(elementTypeName)!;
return typeof(List<>).MakeGenericType(elementType);
}
return Type.GetType(typeName);
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options)
{
// Handle collection types.
if (value is { IsGenericType: true, GenericTypeArguments.Length: 1 })
{
var elementType = value.GenericTypeArguments.First();
var typedEnumerable = typeof(IEnumerable<>).MakeGenericType(elementType);
if (typedEnumerable.IsAssignableFrom(value))
{
var elementTypeName = value.GetSimpleAssemblyQualifiedName();
JsonSerializer.Serialize(writer, $"{elementTypeName}[]", options);
return;
}
}
var typeName = value.GetSimpleAssemblyQualifiedName();
JsonSerializer.Serialize(writer, typeName, options);
}
}

View file

@ -124,7 +124,7 @@ public static class DependencyInjectionExtensions
/// <param name="httpClientBuilderOptions">An options object that can be used to configure the HTTP client builder.</param>
public static IServiceCollection AddApi(this IServiceCollection services, Type apiType, ElsaClientBuilderOptions? httpClientBuilderOptions = default)
{
var builder = services.AddRefitClient(apiType, _ => CreateRefitSettings(), apiType.Name).ConfigureHttpClient(ConfigureElsaApiHttpClient);
var builder = services.AddRefitClient(apiType, sp => CreateRefitSettings(sp, httpClientBuilderOptions?.ConfigureJsonSerializerOptions), apiType.Name).ConfigureHttpClient(ConfigureElsaApiHttpClient);
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
httpClientBuilderOptions?.ConfigureRetryPolicy?.Invoke(builder);
return services;
@ -139,7 +139,7 @@ public static class DependencyInjectionExtensions
public static void AddApiWithoutRetryPolicy<T>(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class
{
var builder = services
.AddRefitClient<T>(_ => CreateRefitSettings(), typeof(T).Name)
.AddRefitClient<T>(sp => CreateRefitSettings(sp), typeof(T).Name)
.ConfigureHttpClient(ConfigureElsaApiHttpClient);
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
}
@ -156,7 +156,7 @@ public static class DependencyInjectionExtensions
/// Creates an API client for the specified API type.
public static T CreateApi<T>(this IServiceProvider serviceProvider, HttpClient httpClient) where T : class
{
return RestService.For<T>(httpClient, CreateRefitSettings());
return RestService.For<T>(httpClient, CreateRefitSettings(serviceProvider));
}
private static void ConfigureElsaApiHttpClient(IServiceProvider serviceProvider, HttpClient httpClient)

View file

@ -10,17 +10,12 @@ namespace Elsa.Api.Client;
/// </summary>
public static class RefitSettingsHelper
{
private static JsonSerializerOptions? _jsonSerializerOptions;
/// <summary>
/// Creates a <see cref="RefitSettings"/> instance configured for Elsa.
/// </summary>
public static RefitSettings CreateRefitSettings()
public static RefitSettings CreateRefitSettings(IServiceProvider serviceProvider, Action<IServiceProvider, JsonSerializerOptions>? configureJsonSerializerOptions = null)
{
var settings = new RefitSettings
{
ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions())
};
var settings = new RefitSettings { ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions(serviceProvider, configureJsonSerializerOptions)) };
return settings;
}
@ -28,11 +23,8 @@ public static class RefitSettingsHelper
/// <summary>
/// Creates a <see cref="JsonSerializerOptions"/> instance configured for Elsa.
/// </summary>
public static JsonSerializerOptions CreateJsonSerializerOptions()
public static JsonSerializerOptions CreateJsonSerializerOptions(IServiceProvider serviceProvider, Action<IServiceProvider, JsonSerializerOptions>? configureJsonSerializerOptions = null)
{
if (_jsonSerializerOptions != null)
return _jsonSerializerOptions;
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
@ -40,7 +32,10 @@ public static class RefitSettingsHelper
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(new VersionOptionsJsonConverter());
options.Converters.Add(new TypeJsonConverter());
return _jsonSerializerOptions = options;
configureJsonSerializerOptions?.Invoke(serviceProvider, options);
return options;
}
}

View file

@ -1,3 +1,4 @@
using System.Text.Json;
using Elsa.Api.Client.HttpMessageHandlers;
using Microsoft.Extensions.DependencyInjection;
using Polly;
@ -39,4 +40,9 @@ public class ElsaClientBuilderOptions
/// Gets or sets a delegate that can be used to configure the retry policy.
/// </summary>
public Action<IHttpClientBuilder>? ConfigureRetryPolicy { get; set; } = builder => builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))));
/// <summary>
/// Gets or sets a delegate that can be used to configure the JSON serializer options.
/// </summary>
public Action<IServiceProvider, JsonSerializerOptions>? ConfigureJsonSerializerOptions { get; set; }
}

View file

@ -9,7 +9,7 @@ namespace Elsa.Workflows.Serialization.Converters;
/// <summary>
/// Serializes <see cref="Type"/> objects to a simple alias representing the type.
/// </summary>
[PublicAPI]
[UsedImplicitly]
public class TypeJsonConverter : JsonConverter<Type>
{
private readonly IWellKnownTypeRegistry _wellKnownTypeRegistry;