Refactor serializer options usage and enhance caching in workflow serialization (#5275)
* Refactor serializer options usage and enhance caching in workflow serialization This update refactors multiple classes dealing with JSON serialization. The usage of serializer options now includes a caching mechanism to avoid unnecessary recomputation. 'CreateOptions' method is replaced by a 'GetOptions' method that considers cached options. Classes are adjusted to clone options when necessary, ensuring a fresh and accurate context each time. * Refactor serializer options in workflow serialization This commit refactors how options are handled for JSON serializers in Elsa's workflow modules. JSON serializer options are now handled more efficiently, by directly using the provided options in `JsonIgnoreCompositeRootConverter` and setting up internal serializer options in `ObjectConverter`. Unnecessary methods related to the cloning of serializer options have been removed.
This commit is contained in:
parent
f663ad3813
commit
1e6aee4bba
|
|
@ -19,7 +19,7 @@ public class AlterationSerializer : ConfigurableSerializer, IAlterationSerialize
|
|||
[RequiresUnreferencedCode("The type of the alteration must be known at compile time.")]
|
||||
public string Serialize(IAlteration alteration)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Serialize(alteration, options);
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ public class AlterationSerializer : ConfigurableSerializer, IAlterationSerialize
|
|||
[RequiresUnreferencedCode("The type of the alteration must be known at compile time.")]
|
||||
public string SerializeMany(IEnumerable<IAlteration> alterations)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Serialize(alterations.ToArray(), options);
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ public class AlterationSerializer : ConfigurableSerializer, IAlterationSerialize
|
|||
[RequiresUnreferencedCode("The type of the alteration must be known at compile time.")]
|
||||
public IAlteration Deserialize(string json)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Deserialize<IAlteration>(json, options)!;
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ public class AlterationSerializer : ConfigurableSerializer, IAlterationSerialize
|
|||
[RequiresUnreferencedCode("The type of the alteration must be known at compile time.")]
|
||||
public IEnumerable<IAlteration> DeserializeMany(string json)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Deserialize<IAlteration[]>(json, options)!;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ public interface IJsonSerializer
|
|||
/// <summary>
|
||||
/// Returns the serializer options.
|
||||
/// </summary>
|
||||
JsonSerializerOptions CreateOptions();
|
||||
JsonSerializerOptions GetOptions();
|
||||
|
||||
/// <summary>
|
||||
/// Applies the specified options.
|
||||
|
|
|
|||
|
|
@ -19,4 +19,12 @@ public static class JsonSerializerOptionsExtensions
|
|||
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones the options.
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions Clone(this JsonSerializerOptions options)
|
||||
{
|
||||
return new JsonSerializerOptions(options);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ namespace Elsa.Common.Serialization;
|
|||
/// </summary>
|
||||
public abstract class ConfigurableSerializer
|
||||
{
|
||||
private JsonSerializerOptions? _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigurableSerializer"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -21,7 +23,7 @@ public abstract class ConfigurableSerializer
|
|||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the service provider.
|
||||
/// </summary>
|
||||
|
|
@ -30,13 +32,17 @@ public abstract class ConfigurableSerializer
|
|||
/// <summary>
|
||||
/// Creates a new instance of <see cref="JsonSerializerOptions"/> with the configured options.
|
||||
/// </summary>
|
||||
public virtual JsonSerializerOptions CreateOptions()
|
||||
public virtual JsonSerializerOptions GetOptions()
|
||||
{
|
||||
if (_options != null)
|
||||
return _options;
|
||||
|
||||
var options = CreateOptionsInternal();
|
||||
ApplyOptions(options);
|
||||
_options = options;
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="JsonSerializerOptions"/> with the configured options.
|
||||
/// </summary>
|
||||
|
|
@ -46,6 +52,17 @@ public abstract class ConfigurableSerializer
|
|||
AddConverters(options);
|
||||
RunConfigurators(options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="JsonSerializerOptions"/>.
|
||||
/// </summary>
|
||||
protected JsonSerializerOptions GetOptionsInternal()
|
||||
{
|
||||
var options = CreateOptionsInternal();
|
||||
ApplyOptions(options);
|
||||
_options = options;
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="JsonSerializerOptions"/>.
|
||||
|
|
@ -59,7 +76,7 @@ public abstract class ConfigurableSerializer
|
|||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
|
||||
};
|
||||
|
||||
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
options.Converters.Add(JsonMetadataServices.TimeSpanConverter);
|
||||
options.Converters.Add(new IntegerJsonConverter());
|
||||
|
|
@ -89,14 +106,14 @@ public abstract class ConfigurableSerializer
|
|||
{
|
||||
var configurators = ServiceProvider.GetServices<ISerializationOptionsConfigurator>();
|
||||
var modifiers = new List<Action<JsonTypeInfo>>();
|
||||
|
||||
|
||||
foreach (var configurator in configurators)
|
||||
{
|
||||
configurator.Configure(options);
|
||||
var modifiersToAdd = configurator.GetModifiers();
|
||||
modifiers.AddRange(modifiersToAdd);
|
||||
}
|
||||
|
||||
|
||||
options.TypeInfoResolver = new ModifiableJsonTypeInfoResolver(modifiers);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public class StandardJsonSerializer : ConfigurableSerializer, IJsonSerializer
|
|||
[RequiresUnreferencedCode("The type is not known at compile time.")]
|
||||
public string Serialize(object value)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Serialize(value, options);
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ public class StandardJsonSerializer : ConfigurableSerializer, IJsonSerializer
|
|||
[RequiresUnreferencedCode("The type is not known at compile time.")]
|
||||
public string Serialize(object value, Type type)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Serialize(value, type, options);
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ public class StandardJsonSerializer : ConfigurableSerializer, IJsonSerializer
|
|||
[RequiresUnreferencedCode("The type is not known at compile time.")]
|
||||
public object Deserialize(string json)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Deserialize<object>(json, options)!;
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ public class StandardJsonSerializer : ConfigurableSerializer, IJsonSerializer
|
|||
[RequiresUnreferencedCode("The type is not known at compile time.")]
|
||||
public object Deserialize(string json, Type type)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Deserialize(json, type, options)!;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,17 +29,17 @@ public static class ObjectConverter
|
|||
/// <summary>
|
||||
/// Attempts to convert the source value into the destination type.
|
||||
/// </summary>
|
||||
public static Result TryConvertTo<T>(this object? value, ObjectConverterOptions? serializerOptions = null) => value.TryConvertTo(typeof(T), serializerOptions);
|
||||
public static Result TryConvertTo<T>(this object? value, ObjectConverterOptions? converterOptions = null) => value.TryConvertTo(typeof(T), converterOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to convert the source value into the destination type.
|
||||
/// </summary>
|
||||
[RequiresUnreferencedCode("The JsonSerializer type is not trim-compatible.")]
|
||||
public static Result TryConvertTo(this object? value, Type targetType, ObjectConverterOptions? serializerOptions = null)
|
||||
public static Result TryConvertTo(this object? value, Type targetType, ObjectConverterOptions? converterOptions = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var convertedValue = value.ConvertTo(targetType, serializerOptions);
|
||||
var convertedValue = value.ConvertTo(targetType, converterOptions);
|
||||
return new Result(true, convertedValue, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -52,7 +52,24 @@ public static class ObjectConverter
|
|||
/// Attempts to convert the source value into the destination type.
|
||||
/// </summary>
|
||||
[RequiresUnreferencedCode("The JsonSerializer type is not trim-compatible.")]
|
||||
public static T? ConvertTo<T>(this object? value, ObjectConverterOptions? serializerOptions = null) => value != null ? (T?)value.ConvertTo(typeof(T), serializerOptions) : default;
|
||||
public static T? ConvertTo<T>(this object? value, ObjectConverterOptions? converterOptions = null) => value != null ? (T?)value.ConvertTo(typeof(T), converterOptions) : default;
|
||||
|
||||
private static JsonSerializerOptions? _defaultSerializerOptions;
|
||||
private static JsonSerializerOptions? _internalSerializerOptions;
|
||||
|
||||
private static JsonSerializerOptions DefaultSerializerOptions => _defaultSerializerOptions ??= new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
ReferenceHandler = ReferenceHandler.Preserve,
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
|
||||
};
|
||||
|
||||
private static JsonSerializerOptions InternalSerializerOptions => _internalSerializerOptions ??= new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to convert the source value into the destination type.
|
||||
|
|
@ -68,18 +85,7 @@ public static class ObjectConverter
|
|||
if (sourceType == targetType)
|
||||
return value;
|
||||
|
||||
var serializerOptions = converterOptions?.SerializerOptions != null ? new JsonSerializerOptions(converterOptions.SerializerOptions) : new JsonSerializerOptions();
|
||||
serializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
serializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
|
||||
serializerOptions.PropertyNameCaseInsensitive = true;
|
||||
serializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
serializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
|
||||
|
||||
var internalSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
|
||||
};
|
||||
|
||||
var serializerOptions = converterOptions?.SerializerOptions ?? DefaultSerializerOptions;
|
||||
var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
||||
var underlyingSourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
|
||||
|
||||
|
|
@ -133,6 +139,8 @@ public static class ObjectConverter
|
|||
if (IsDateType(underlyingSourceType) && IsDateType(underlyingTargetType))
|
||||
return ConvertAnyDateType(value, underlyingTargetType);
|
||||
|
||||
var internalSerializerOptions = InternalSerializerOptions;
|
||||
|
||||
if (typeof(IDictionary<string, object>).IsAssignableFrom(underlyingSourceType) && underlyingTargetType.IsClass)
|
||||
{
|
||||
if (typeof(ExpandoObject) == underlyingTargetType)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Unicode;
|
||||
using Elsa.Common.Converters;
|
||||
using Elsa.Expressions.Contracts;
|
||||
using Elsa.Expressions.Helpers;
|
||||
|
|
@ -23,9 +26,16 @@ public class ObjectExpressionHandler : IExpressionHandler
|
|||
if (string.IsNullOrWhiteSpace(value))
|
||||
return ValueTask.FromResult(default(object?));
|
||||
|
||||
var serializerOptions = new JsonSerializerOptions();
|
||||
var serializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
ReferenceHandler = ReferenceHandler.Preserve,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
|
||||
};
|
||||
serializerOptions.Converters.Add(new IntegerJsonConverter());
|
||||
serializerOptions.Converters.Add(new DecimalJsonConverter());
|
||||
serializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
|
||||
var converterOptions = new ObjectConverterOptions(serializerOptions);
|
||||
var model = value.ConvertTo(returnType, converterOptions);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
using System.Collections;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Unicode;
|
||||
using Elsa.Common.Contracts;
|
||||
using Elsa.Expressions.Helpers;
|
||||
using Elsa.Expressions.Models;
|
||||
|
|
@ -350,6 +354,19 @@ public static class ExpressionExecutionContextExtensions
|
|||
{
|
||||
return context.GetInput<T>(inputDefinition.Name);
|
||||
}
|
||||
|
||||
private static JsonSerializerOptions? _serializerOptions;
|
||||
|
||||
private static JsonSerializerOptions GetSerializerOptions(ExpressionExecutionContext context)
|
||||
{
|
||||
if(_serializerOptions != null)
|
||||
return _serializerOptions;
|
||||
|
||||
var serializerOptions = context.GetRequiredService<IJsonSerializer>().GetOptions().Clone();
|
||||
serializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
|
||||
_serializerOptions = serializerOptions;
|
||||
return serializerOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the specified input.
|
||||
|
|
@ -361,7 +378,7 @@ public static class ExpressionExecutionContextExtensions
|
|||
public static T? GetInput<T>(this ExpressionExecutionContext context, string name)
|
||||
{
|
||||
var value = context.GetInput(name);
|
||||
var serializerOptions = context.GetRequiredService<IJsonSerializer>().CreateOptions();
|
||||
var serializerOptions = GetSerializerOptions(context);
|
||||
var converterOptions = new ObjectConverterOptions(serializerOptions);
|
||||
return value.ConvertTo<T>(converterOptions);
|
||||
}
|
||||
|
|
@ -432,7 +449,6 @@ public static class ExpressionExecutionContextExtensions
|
|||
{
|
||||
var activity = activityWithOutput.Activity;
|
||||
var activityDescriptor = activityWithOutput.ActivityDescriptor;
|
||||
|
||||
var activityIdentifier = useActivityName ? activity.Name : activity.Id;
|
||||
var activityIdPascalName = activityIdentifier.Pascalize();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Unicode;
|
||||
using Elsa.Expressions.Helpers;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Memory;
|
||||
|
|
@ -55,9 +58,16 @@ public static class VariableExtensions
|
|||
public static object? ParseValue(this Variable variable, object? value)
|
||||
{
|
||||
var genericType = variable.GetType().GenericTypeArguments.FirstOrDefault();
|
||||
var jsonSerializerOptions = new JsonSerializerOptions();
|
||||
jsonSerializerOptions.Converters.Add(new ExpandoObjectConverterFactory());
|
||||
var converterOptions = new ObjectConverterOptions(jsonSerializerOptions);
|
||||
var serializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
ReferenceHandler = ReferenceHandler.Preserve,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
|
||||
};
|
||||
serializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
serializerOptions.Converters.Add(new ExpandoObjectConverterFactory());
|
||||
var converterOptions = new ObjectConverterOptions(serializerOptions);
|
||||
return genericType == null ? value : value?.ConvertTo(genericType, converterOptions);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public class ActivityJsonConverter : JsonConverter<IActivity>
|
|||
private readonly IExpressionDescriptorRegistry _expressionDescriptorRegistry;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<ActivityJsonConverter> _logger;
|
||||
private JsonSerializerOptions? _options;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ActivityJsonConverter(
|
||||
|
|
@ -55,9 +56,7 @@ public class ActivityJsonConverter : JsonConverter<IActivity>
|
|||
activityTypeName = GetActivityDetails(activityRoot, out activityTypeVersion, out activityDescriptor);
|
||||
}
|
||||
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider));
|
||||
newOptions.Converters.Add(new OutputJsonConverterFactory(_serviceProvider));
|
||||
var newOptions = GetClonedOptions(options);
|
||||
|
||||
// If the activity type is not found, create a NotFoundActivity instead.
|
||||
if (activityDescriptor == null)
|
||||
|
|
@ -84,10 +83,7 @@ public class ActivityJsonConverter : JsonConverter<IActivity>
|
|||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, IActivity value, JsonSerializerOptions options)
|
||||
{
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
|
||||
newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider));
|
||||
newOptions.Converters.Add(new OutputJsonConverterFactory(_serviceProvider));
|
||||
var newOptions = GetClonedOptions(options);
|
||||
|
||||
// Write to a JsonObject so that we can add additional information.
|
||||
var activityModel = JsonSerializer.SerializeToNode(value, value.GetType(), newOptions)!;
|
||||
|
|
@ -212,4 +208,15 @@ public class ActivityJsonConverter : JsonConverter<IActivity>
|
|||
|
||||
return activityTypeName;
|
||||
}
|
||||
|
||||
private JsonSerializerOptions GetClonedOptions(JsonSerializerOptions options)
|
||||
{
|
||||
if(_options != null)
|
||||
return _options;
|
||||
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.Add(new InputJsonConverterFactory(_serviceProvider));
|
||||
newOptions.Converters.Add(new OutputJsonConverterFactory(_serviceProvider));
|
||||
return _options = newOptions;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ namespace Elsa.Workflows.Serialization.Converters;
|
|||
/// </summary>
|
||||
public class ExcludeFromHashConverter : JsonConverter<object>
|
||||
{
|
||||
private JsonSerializerOptions? _options;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
|
|
@ -21,8 +23,7 @@ public class ExcludeFromHashConverter : JsonConverter<object>
|
|||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.RemoveWhere(x => x is ExcludeFromHashConverterFactory);
|
||||
var newOptions = GetClonedOptions(options);
|
||||
|
||||
foreach (var property in value.GetType().GetProperties())
|
||||
{
|
||||
|
|
@ -39,6 +40,16 @@ public class ExcludeFromHashConverter : JsonConverter<object>
|
|||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private JsonSerializerOptions GetClonedOptions(JsonSerializerOptions options)
|
||||
{
|
||||
if(_options != null)
|
||||
return _options;
|
||||
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.RemoveWhere(x => x is ExcludeFromHashConverterFactory);
|
||||
return _options = newOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ public class JsonIgnoreCompositeRootConverter : JsonConverter<IActivity>
|
|||
writer.WriteStartObject();
|
||||
|
||||
var properties = value?.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) ?? Array.Empty<PropertyInfo>();
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
|
|
@ -45,7 +44,7 @@ public class JsonIgnoreCompositeRootConverter : JsonConverter<IActivity>
|
|||
continue;
|
||||
}
|
||||
|
||||
JsonSerializer.Serialize(writer, input, newOptions);
|
||||
JsonSerializer.Serialize(writer, input, options);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
/// <inheritdoc />
|
||||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
var newOptions = options.Clone();
|
||||
|
||||
if (reader.TokenType != JsonTokenType.StartObject && reader.TokenType != JsonTokenType.StartArray)
|
||||
return ReadPrimitive(ref reader, newOptions);
|
||||
|
|
@ -117,7 +117,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
throw new InvalidOperationException($"Cannot determine the element type of array '{targetType}'.");
|
||||
|
||||
var model = JsonElement.ParseValue(ref reader);
|
||||
var referenceResolver = (options.ReferenceHandler as CrossScopedReferenceHandler)?.GetResolver();
|
||||
var referenceResolver = (newOptions.ReferenceHandler as CrossScopedReferenceHandler)?.GetResolver();
|
||||
|
||||
if (model.TryGetProperty(RefPropertyName, out var refProperty))
|
||||
{
|
||||
|
|
@ -168,7 +168,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
return;
|
||||
}
|
||||
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
var newOptions = options.Clone();
|
||||
var type = value.GetType();
|
||||
|
||||
if (type.IsPrimitive || value is string or decimal or DateTimeOffset or DateTime or DateOnly or TimeOnly or JsonElement or Guid or TimeSpan or Uri or Version or Enum)
|
||||
|
|
@ -196,7 +196,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
// Determine if the value is going to be serialized for the first time.
|
||||
// Later on, we need to know this information to determine if we need to write the type name or not, so that we can reconstruct the actual type when deserializing.
|
||||
var shouldWriteTypeField = true;
|
||||
var referenceResolver = (CustomPreserveReferenceResolver?)(options.ReferenceHandler as CrossScopedReferenceHandler)?.GetResolver();
|
||||
var referenceResolver = (CustomPreserveReferenceResolver?)(newOptions.ReferenceHandler as CrossScopedReferenceHandler)?.GetResolver();
|
||||
|
||||
if (referenceResolver != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ namespace Elsa.Workflows.Serialization.Converters;
|
|||
/// </summary>
|
||||
public class SafeValueConverter : JsonConverter<object>
|
||||
{
|
||||
private JsonSerializerOptions? _options;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var newOptions = CreateNewOptions(options);
|
||||
var newOptions = GetClonedOptions(options);
|
||||
return JsonSerializer.Deserialize(ref reader, typeToConvert, newOptions)!;
|
||||
}
|
||||
|
||||
|
|
@ -22,7 +24,7 @@ public class SafeValueConverter : JsonConverter<object>
|
|||
{
|
||||
try
|
||||
{
|
||||
var newOptions = CreateNewOptions(options);
|
||||
var newOptions = GetClonedOptions(options);
|
||||
|
||||
// Serialize the value to a temporary string.
|
||||
var serializedValue = JsonSerializer.Serialize(value, newOptions);
|
||||
|
|
@ -40,10 +42,14 @@ public class SafeValueConverter : JsonConverter<object>
|
|||
}
|
||||
}
|
||||
|
||||
private JsonSerializerOptions CreateNewOptions(JsonSerializerOptions options)
|
||||
private JsonSerializerOptions GetClonedOptions(JsonSerializerOptions options)
|
||||
{
|
||||
if(_options != null)
|
||||
return _options;
|
||||
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.RemoveWhere(x => x is SafeValueConverterFactory);
|
||||
_options = newOptions;
|
||||
return newOptions;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ namespace Elsa.Workflows.Serialization.Converters;
|
|||
public class VariableConverter : JsonConverter<Variable>
|
||||
{
|
||||
private readonly VariableMapper _mapper;
|
||||
private JsonSerializerOptions? _options;
|
||||
|
||||
/// <inheritdoc />
|
||||
// ReSharper disable once ContextualLoggerProblem
|
||||
|
|
@ -25,8 +26,7 @@ public class VariableConverter : JsonConverter<Variable>
|
|||
/// <inheritdoc />
|
||||
public override Variable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.Add(new JsonPrimitiveToStringConverter());
|
||||
var newOptions = GetClonedOptions(options);
|
||||
var model = JsonSerializer.Deserialize<VariableModel>(ref reader, newOptions)!;
|
||||
var variable = _mapper.Map(model);
|
||||
|
||||
|
|
@ -39,4 +39,15 @@ public class VariableConverter : JsonConverter<Variable>
|
|||
var model = _mapper.Map(value);
|
||||
JsonSerializer.Serialize(writer, model, options);
|
||||
}
|
||||
|
||||
private JsonSerializerOptions GetClonedOptions(JsonSerializerOptions options)
|
||||
{
|
||||
if(_options != null)
|
||||
return _options;
|
||||
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.Add(new JsonPrimitiveToStringConverter());
|
||||
_options = newOptions;
|
||||
return newOptions;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ public class ApiSerializer : ConfigurableSerializer, IApiSerializer
|
|||
/// <inheritdoc />
|
||||
public string Serialize(object model)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Serialize(model, options);
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ public class ApiSerializer : ConfigurableSerializer, IApiSerializer
|
|||
/// <inheritdoc />
|
||||
public T Deserialize<T>(string serializedModel)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return JsonSerializer.Deserialize<T>(serializedModel, options)!;
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ public class ApiSerializer : ConfigurableSerializer, IApiSerializer
|
|||
options.Converters.Add(CreateInstance<TypeJsonConverter>());
|
||||
}
|
||||
|
||||
JsonSerializerOptions IApiSerializer.CreateOptions() => base.CreateOptions();
|
||||
JsonSerializerOptions IApiSerializer.CreateOptions() => GetOptions();
|
||||
|
||||
JsonSerializerOptions IApiSerializer.ApplyOptions(JsonSerializerOptions options)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,44 +1,38 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Common.Serialization;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Serialization.Converters;
|
||||
|
||||
namespace Elsa.Workflows.Serialization.Serializers;
|
||||
|
||||
/// <inheritdoc cref="IActivitySerializer" />
|
||||
public class JsonActivitySerializer : ConfigurableSerializer, IActivitySerializer
|
||||
public class JsonActivitySerializer(IServiceProvider serviceProvider) : ConfigurableSerializer(serviceProvider), IActivitySerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonActivitySerializer"/> class.
|
||||
/// </summary>
|
||||
public JsonActivitySerializer(IServiceProvider serviceProvider) : base(serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
private JsonSerializerOptions? _options;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Serialize(IActivity activity)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
options.Converters.Add(CreateInstance<JsonIgnoreCompositeRootConverterFactory>());
|
||||
var options = GetOptionsInternal();
|
||||
return JsonSerializer.Serialize(activity, activity.GetType(), options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Serialize(object value)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
options.Converters.Add(CreateInstance<JsonIgnoreCompositeRootConverterFactory>());
|
||||
var options = GetOptionsInternal();
|
||||
return JsonSerializer.Serialize(value, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IActivity Deserialize(string serializedActivity) => JsonSerializer.Deserialize<IActivity>(serializedActivity, CreateOptions())!;
|
||||
public IActivity Deserialize(string serializedActivity) => JsonSerializer.Deserialize<IActivity>(serializedActivity, GetOptions())!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Deserialize(string serializedValue, Type type) => JsonSerializer.Deserialize(serializedValue, type, CreateOptions())!;
|
||||
public object Deserialize(string serializedValue, Type type) => JsonSerializer.Deserialize(serializedValue, type, GetOptions())!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public T Deserialize<T>(string serializedValue) => JsonSerializer.Deserialize<T>(serializedValue, CreateOptions())!;
|
||||
public T Deserialize<T>(string serializedValue) => JsonSerializer.Deserialize<T>(serializedValue, GetOptions())!;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void AddConverters(JsonSerializerOptions options)
|
||||
|
|
@ -46,4 +40,14 @@ public class JsonActivitySerializer : ConfigurableSerializer, IActivitySerialize
|
|||
options.Converters.Add(CreateInstance<TypeJsonConverter>());
|
||||
options.Converters.Add(CreateInstance<InputJsonConverterFactory>());
|
||||
}
|
||||
|
||||
private JsonSerializerOptions GetOptionsInternal()
|
||||
{
|
||||
if(_options != null)
|
||||
return _options;
|
||||
|
||||
var options = GetOptions().Clone();
|
||||
options.Converters.Add(CreateInstance<JsonIgnoreCompositeRootConverterFactory>());
|
||||
return _options = options;
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat
|
|||
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
|
||||
public Task<string> SerializeAsync(WorkflowState workflowState, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return Task.FromResult(JsonSerializer.Serialize(workflowState, options));
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat
|
|||
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
|
||||
public Task<byte[]> SerializeToUtfBytesAsync(WorkflowState workflowState, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return Task.FromResult(JsonSerializer.SerializeToUtf8Bytes(workflowState, options));
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat
|
|||
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
|
||||
public Task<JsonElement> SerializeToElementAsync(WorkflowState workflowState, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return Task.FromResult(JsonSerializer.SerializeToElement(workflowState, options));
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat
|
|||
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
|
||||
public Task<string> SerializeAsync(object workflowState, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
var json = JsonSerializer.Serialize(workflowState, workflowState.GetType(), options);
|
||||
return Task.FromResult(json);
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat
|
|||
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
|
||||
public Task<WorkflowState> DeserializeAsync(string serializedState, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
var workflowState = JsonSerializer.Deserialize<WorkflowState>(serializedState, options)!;
|
||||
return Task.FromResult(workflowState);
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat
|
|||
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
|
||||
public Task<WorkflowState> DeserializeAsync(JsonElement serializedState, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
var workflowState = serializedState.Deserialize<WorkflowState>(options)!;
|
||||
return Task.FromResult(workflowState);
|
||||
}
|
||||
|
|
@ -84,11 +84,18 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat
|
|||
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
|
||||
public Task<T> DeserializeAsync<T>(string serializedState, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
var workflowState = JsonSerializer.Deserialize<T>(serializedState, options)!;
|
||||
return Task.FromResult(workflowState);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonSerializerOptions GetOptions()
|
||||
{
|
||||
// Bypass cached options to ensure that the reference handler is always fresh.
|
||||
return GetOptionsInternal();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Configure(JsonSerializerOptions options)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public class SafeSerializer : ConfigurableSerializer, ISafeSerializer
|
|||
[RequiresUnreferencedCode("The type T may be trimmed.")]
|
||||
public ValueTask<string> SerializeAsync(object? value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return ValueTask.FromResult(JsonSerializer.Serialize(value, options));
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ public class SafeSerializer : ConfigurableSerializer, ISafeSerializer
|
|||
[RequiresUnreferencedCode("The type T may be trimmed.")]
|
||||
public ValueTask<JsonElement> SerializeToElementAsync(object? value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return new(JsonSerializer.SerializeToElement(value, options));
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ public class SafeSerializer : ConfigurableSerializer, ISafeSerializer
|
|||
[RequiresUnreferencedCode("The type T may be trimmed.")]
|
||||
public ValueTask<T> DeserializeAsync<T>(string json, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return new(JsonSerializer.Deserialize<T>(json, options)!);
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ public class SafeSerializer : ConfigurableSerializer, ISafeSerializer
|
|||
[RequiresUnreferencedCode("The type T may be trimmed.")]
|
||||
public ValueTask<T> DeserializeAsync<T>(JsonElement element, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var options = GetOptions();
|
||||
return new(element.Deserialize<T>(options)!);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
|
|
@ -12,13 +14,14 @@ namespace Elsa.Workflows.Management.Services;
|
|||
/// </summary>
|
||||
public class WorkflowSerializer(IApiSerializer apiSerializer, WorkflowDefinitionMapper workflowDefinitionMapper) : IWorkflowSerializer
|
||||
{
|
||||
private JsonSerializerOptions? _writeOptions;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Serialize(Workflow workflow)
|
||||
{
|
||||
var model = workflowDefinitionMapper.Map(workflow);
|
||||
var serializerOptions = apiSerializer.CreateOptions();
|
||||
serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory());
|
||||
return apiSerializer.Serialize(model);
|
||||
var serializerOptions = GetWriteOptionsInternal();
|
||||
return JsonSerializer.Serialize(model, serializerOptions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -27,4 +30,14 @@ public class WorkflowSerializer(IApiSerializer apiSerializer, WorkflowDefinition
|
|||
var model = apiSerializer.Deserialize<WorkflowDefinitionModel>(serializedWorkflow);
|
||||
return workflowDefinitionMapper.Map(model);
|
||||
}
|
||||
|
||||
private JsonSerializerOptions GetWriteOptionsInternal()
|
||||
{
|
||||
if(_writeOptions != null)
|
||||
return _writeOptions;
|
||||
|
||||
var options = apiSerializer.CreateOptions().Clone();
|
||||
options.Converters.Add(new JsonIgnoreCompositeRootConverterFactory());
|
||||
return _writeOptions = options;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue