Update serialization and conversion functions (#5682)
* Update serialization and conversion functions Updated JSON conversion to be able to use type aliases when saving serialized data to the DB. * Simplify and update type alias registration. Removed redundant XML comments and replaced IDictionary type aliases with Dictionary equivalents for consistency. These changes improve code readability and maintainability. * Use collection initializer syntax * Change "_type" values to "ObjectDictionary" in test files Updated the "_type" field in various JSON test files from multiple different values to a unified "ObjectDictionary". This ensures consistency across the test files and aligns with the expected type format. --------- Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
This commit is contained in:
parent
1be3b43c76
commit
6f36cfff0c
|
|
@ -60,6 +60,11 @@ public class ActivityExecutionRecordRecord
|
|||
/// </summary>
|
||||
public string? SerializedException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any properties provided by the activity.
|
||||
/// </summary>
|
||||
public string? SerializedProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time at which the activity execution began.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -130,10 +130,11 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore
|
|||
HasBookmarks = source.HasBookmarks,
|
||||
Status = source.Status.ToString(),
|
||||
ActivityTypeVersion = source.ActivityTypeVersion,
|
||||
SerializedActivityState = source.ActivityState != null ? await _safeSerializer.SerializeAsync(source.ActivityState, cancellationToken) : default,
|
||||
SerializedPayload = source.Payload != null ? await _safeSerializer.SerializeAsync(source.Payload, cancellationToken) : default,
|
||||
SerializedOutputs = source.Outputs != null ? await _safeSerializer.SerializeAsync(source.Outputs, cancellationToken) : default,
|
||||
SerializedException = source.Exception != null ? _payloadSerializer.Serialize(source.Exception) : default
|
||||
SerializedActivityState = source.ActivityState != null ? await _safeSerializer.SerializeAsync(source.ActivityState, cancellationToken) : null,
|
||||
SerializedPayload = source.Payload != null ? await _safeSerializer.SerializeAsync(source.Payload, cancellationToken) : null,
|
||||
SerializedOutputs = source.Outputs?.Any() == true ? await _safeSerializer.SerializeAsync(source.Outputs, cancellationToken) : null,
|
||||
SerializedException = source.Exception != null ? _payloadSerializer.Serialize(source.Exception) : null,
|
||||
SerializedProperties = source.Properties.Any() ? await _safeSerializer.SerializeAsync(source.Properties, cancellationToken) : null
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +156,8 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore
|
|||
ActivityState = source.SerializedActivityState != null ? _payloadSerializer.Deserialize<IDictionary<string, object>>(source.SerializedActivityState) : default,
|
||||
Payload = source.SerializedPayload != null ? await _safeSerializer.DeserializeAsync<IDictionary<string, object>>(source.SerializedPayload, cancellationToken) : default,
|
||||
Outputs = source.SerializedOutputs != null ? await _safeSerializer.DeserializeAsync<IDictionary<string, object?>>(source.SerializedOutputs, cancellationToken) : default,
|
||||
Exception = source.SerializedException != null ? _payloadSerializer.Deserialize<ExceptionState>(source.SerializedException) : default
|
||||
Exception = source.SerializedException != null ? _payloadSerializer.Deserialize<ExceptionState>(source.SerializedException) : default,
|
||||
Properties = source.SerializedProperties != null ? await _safeSerializer.DeserializeAsync<IDictionary<string, object>>(source.SerializedProperties, cancellationToken) : default
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,15 +83,15 @@ public class EFCoreActivityExecutionStore(
|
|||
{
|
||||
entity = entity.SanitizeLogMessage();
|
||||
var compressionAlgorithm = options.Value.CompressionAlgorithm ?? nameof(None);
|
||||
var serializedActivityState = entity.ActivityState != null ? await safeSerializer.SerializeAsync(entity.ActivityState, cancellationToken) : default;
|
||||
var compressedSerializedActivityState = serializedActivityState != null ? await compressionCodecResolver.Resolve(compressionAlgorithm).CompressAsync(serializedActivityState, cancellationToken) : default;
|
||||
var serializedActivityState = entity.ActivityState != null ? await safeSerializer.SerializeAsync(entity.ActivityState, cancellationToken) : null;
|
||||
var compressedSerializedActivityState = serializedActivityState != null ? await compressionCodecResolver.Resolve(compressionAlgorithm).CompressAsync(serializedActivityState, cancellationToken) : null;
|
||||
|
||||
dbContext.Entry(entity).Property("SerializedActivityState").CurrentValue = compressedSerializedActivityState;
|
||||
dbContext.Entry(entity).Property("SerializedActivityStateCompressionAlgorithm").CurrentValue = compressionAlgorithm;
|
||||
dbContext.Entry(entity).Property("SerializedOutputs").CurrentValue = entity.Outputs != null && entity.Outputs.Any() ? await safeSerializer.SerializeAsync(entity.Outputs, cancellationToken) : default;
|
||||
dbContext.Entry(entity).Property("SerializedProperties").CurrentValue =entity.Properties.Any() ? payloadSerializer.Serialize(entity.Properties) : default;
|
||||
dbContext.Entry(entity).Property("SerializedException").CurrentValue = entity.Exception != null ? payloadSerializer.Serialize(entity.Exception) : default;
|
||||
dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload != null ? payloadSerializer.Serialize(entity.Payload) : default;
|
||||
dbContext.Entry(entity).Property("SerializedOutputs").CurrentValue = entity.Outputs?.Any() == true ? await safeSerializer.SerializeAsync(entity.Outputs, cancellationToken) : null;
|
||||
dbContext.Entry(entity).Property("SerializedProperties").CurrentValue = entity.Properties.Any() ? payloadSerializer.Serialize(entity.Properties) : null;
|
||||
dbContext.Entry(entity).Property("SerializedException").CurrentValue = entity.Exception != null ? payloadSerializer.Serialize(entity.Exception) : null;
|
||||
dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload?.Any() == true ? payloadSerializer.Serialize(entity.Payload) : null;
|
||||
}
|
||||
|
||||
[RequiresUnreferencedCode("Calls Elsa.EntityFrameworkCore.Modules.Runtime.EFCoreActivityExecutionStore.DeserializeActivityState(RuntimeElsaDbContext, ActivityExecutionRecord, CancellationToken)")]
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public class EFCoreWorkflowExecutionLogStore : IWorkflowExecutionLogStore
|
|||
private async ValueTask OnSaveAsync(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord entity, CancellationToken cancellationToken)
|
||||
{
|
||||
entity = entity.SanitizeLogMessage();
|
||||
dbContext.Entry(entity).Property("SerializedActivityState").CurrentValue = entity.ActivityState != null ? await _safeSerializer.SerializeAsync(entity.ActivityState, cancellationToken) : default;
|
||||
dbContext.Entry(entity).Property("SerializedActivityState").CurrentValue = entity.ActivityState?.Any() == true ? await _safeSerializer.SerializeAsync(entity.ActivityState, cancellationToken) : default;
|
||||
dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload != null ? await _safeSerializer.SerializeAsync(entity.Payload, cancellationToken) : default;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,16 +4,12 @@ using Elsa.Extensions;
|
|||
|
||||
namespace Elsa.Expressions.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Options for the expression feature.
|
||||
/// </summary>
|
||||
public class ExpressionOptions
|
||||
{
|
||||
private readonly IDictionary<string, Type> _aliasTypeDictionary = new Dictionary<string, Type>();
|
||||
|
||||
/// <summary>
|
||||
|
||||
/// Initializes a new instance of the <see cref="ExpressionOptions"/> class.
|
||||
/// </summary>
|
||||
public ExpressionOptions()
|
||||
{
|
||||
AliasTypeDictionary = new ReadOnlyDictionary<string, Type>(_aliasTypeDictionary);
|
||||
|
|
@ -34,23 +30,16 @@ public class ExpressionOptions
|
|||
this.AddTypeAlias<TimeSpan>("TimeSpan");
|
||||
this.AddTypeAlias<ExpandoObject>("ExpandoObject");
|
||||
this.AddTypeAlias<ExpandoObject>("JSON");
|
||||
this.AddTypeAlias<IDictionary<string, string>>("StringDictionary");
|
||||
this.AddTypeAlias<IDictionary<string, string>>("StringMap");
|
||||
this.AddTypeAlias<IDictionary<string, object>>("ObjectMap");
|
||||
this.AddTypeAlias<IDictionary<string, object>>("ObjectDictionary");
|
||||
this.AddTypeAlias<Dictionary<string, string>>("StringDictionary");
|
||||
this.AddTypeAlias<Dictionary<string, string>>("StringMap");
|
||||
this.AddTypeAlias<Dictionary<string, object>>("ObjectMap");
|
||||
this.AddTypeAlias<Dictionary<string, object>>("ObjectDictionary");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
/// Gets the type alias dictionary.
|
||||
/// </summary>
|
||||
public IDictionary<string, Type> AliasTypeDictionary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Registers a well known type alias.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <returns>The options.</returns>
|
||||
|
||||
/// Registers a well-known type alias.
|
||||
public ExpressionOptions RegisterTypeAlias(Type type, string alias)
|
||||
{
|
||||
_aliasTypeDictionary[alias] = type;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Workflows.Activities.Flowchart.Models;
|
||||
using Elsa.Workflows.Activities.Flowchart.Serialization;
|
||||
|
||||
namespace Elsa.Workflows.Features;
|
||||
|
|
@ -19,5 +20,11 @@ public class FlowchartFeature : FeatureBase
|
|||
public override void Apply()
|
||||
{
|
||||
Services.AddSerializationOptionsConfigurator<FlowchartSerializationOptionConfigurator>();
|
||||
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Module.AddTypeAlias<FlowScope>("FlowScope");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Expressions.Contracts;
|
||||
|
||||
namespace Elsa.Workflows.Serialization.Converters;
|
||||
|
||||
|
|
@ -11,9 +12,9 @@ public class PolymorphicDictionaryConverter : JsonConverter<IDictionary<string,
|
|||
private readonly JsonConverter<object> _objectConverter;
|
||||
|
||||
/// <inheritdoc />
|
||||
public PolymorphicDictionaryConverter(JsonSerializerOptions options)
|
||||
public PolymorphicDictionaryConverter(JsonSerializerOptions options, IWellKnownTypeRegistry wellKnownTypeRegistry)
|
||||
{
|
||||
var factory = (JsonConverterFactory)(options.Converters.FirstOrDefault(x => x is PolymorphicObjectConverterFactory) ?? new PolymorphicObjectConverterFactory());
|
||||
var factory = (JsonConverterFactory)(options.Converters.FirstOrDefault(x => x is PolymorphicObjectConverterFactory) ?? new PolymorphicObjectConverterFactory(wellKnownTypeRegistry));
|
||||
_objectConverter = (JsonConverter<object>)factory.CreateConverter(typeof(object), options)!;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System.Collections;
|
||||
using System.Dynamic;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Expressions.Contracts;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Serialization.ReferenceHandlers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
|
@ -14,7 +14,7 @@ namespace Elsa.Workflows.Serialization.Converters;
|
|||
/// <summary>
|
||||
/// Reads objects as primitive types rather than <see cref="JsonElement"/> values while also maintaining the .NET type name for reconstructing the actual type.
|
||||
/// </summary>
|
||||
public class PolymorphicObjectConverter : JsonConverter<object>
|
||||
public class PolymorphicObjectConverter(IWellKnownTypeRegistry wellKnownTypeRegistry) : JsonConverter<object>
|
||||
{
|
||||
private const string TypePropertyName = "_type";
|
||||
private const string ItemsPropertyName = "_items";
|
||||
|
|
@ -23,11 +23,6 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
private const string RefPropertyName = "$ref";
|
||||
private const string ValuesPropertyName = "$values";
|
||||
|
||||
/// <inheritdoc />
|
||||
public PolymorphicObjectConverter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
|
|
@ -145,10 +140,9 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
}
|
||||
else if (isHashSet)
|
||||
{
|
||||
addSetMethod.Invoke(collection, new[]
|
||||
{
|
||||
addSetMethod.Invoke(collection, [
|
||||
deserializedElement
|
||||
});
|
||||
]);
|
||||
}
|
||||
else if (collection is IList list)
|
||||
{
|
||||
|
|
@ -250,13 +244,26 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
if (type != typeof(ExpandoObject))
|
||||
{
|
||||
if (shouldWriteTypeField)
|
||||
writer.WriteString(TypePropertyName, type.GetSimpleAssemblyQualifiedName());
|
||||
{
|
||||
var typeOptions = newOptions.Clone();
|
||||
typeOptions.Converters.RemoveWhere(c => c.GetType() != typeof(TypeJsonConverter));
|
||||
|
||||
if (typeOptions.Converters.Any())
|
||||
{
|
||||
var typeValue = JsonSerializer.Serialize(type, typeOptions).Trim('"');
|
||||
writer.WriteString(TypePropertyName, typeValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteString(TypePropertyName, type.GetSimpleAssemblyQualifiedName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static Type? ReadType(Utf8JsonReader reader)
|
||||
private Type? ReadType(Utf8JsonReader reader)
|
||||
{
|
||||
reader.Read(); // Move to the first token inside the object.
|
||||
string? typeName = null;
|
||||
|
|
@ -298,7 +305,7 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
}
|
||||
|
||||
// If we found the _type property, attempt to resolve the type.
|
||||
var targetType = typeName != null ? Type.GetType(typeName) : default;
|
||||
var targetType = typeName != null ? wellKnownTypeRegistry.TryGetType(typeName, out var type) ? type : Type.GetType(typeName) : default;
|
||||
return targetType;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
using System.Dynamic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Expressions.Contracts;
|
||||
|
||||
namespace Elsa.Workflows.Serialization.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// A JSON converter factory that creates <see cref="PolymorphicObjectConverter"/> instances.
|
||||
/// </summary>
|
||||
public class PolymorphicObjectConverterFactory : JsonConverterFactory
|
||||
public class PolymorphicObjectConverterFactory(IWellKnownTypeRegistry wellKnownTypeRegistry) : JsonConverterFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
|
|
@ -24,8 +25,8 @@ public class PolymorphicObjectConverterFactory : JsonConverterFactory
|
|||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (typeof(IDictionary<string, object>).IsAssignableFrom(typeToConvert))
|
||||
return new PolymorphicDictionaryConverter(options);
|
||||
return new PolymorphicDictionaryConverter(options, wellKnownTypeRegistry);
|
||||
|
||||
return new PolymorphicObjectConverter();
|
||||
return new PolymorphicObjectConverter(wellKnownTypeRegistry);
|
||||
}
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat
|
|||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
options.Converters.Add(new TypeJsonConverter(_wellKnownTypeRegistry));
|
||||
options.Converters.Add(JsonMetadataServices.TimeSpanConverter);
|
||||
options.Converters.Add(new PolymorphicObjectConverterFactory());
|
||||
options.Converters.Add(new PolymorphicObjectConverterFactory(_wellKnownTypeRegistry));
|
||||
options.Converters.Add(new TypeJsonConverter(_wellKnownTypeRegistry));
|
||||
options.Converters.Add(new VariableConverterFactory(_wellKnownTypeRegistry, _loggerFactory));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@
|
|||
"_island": "[\r\n {\r\n \u0022path\u0022: \u0022folder1\u0022,\r\n \u0022command\u0022: \u0022add\u0022\r\n }\r\n]",
|
||||
"_type": "Newtonsoft.Json.Linq.JArray, Newtonsoft.Json"
|
||||
},
|
||||
"_type": "System.Collections.Generic.Dictionary\u00602[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]], System.Private.CoreLib"
|
||||
"_type": "ObjectDictionary"
|
||||
}
|
||||
|
|
@ -6,5 +6,5 @@
|
|||
"command": "add"
|
||||
}
|
||||
],
|
||||
"_type": "System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]], System.Private.CoreLib"
|
||||
"_type": "ObjectDictionary"
|
||||
}
|
||||
|
|
@ -4,5 +4,5 @@
|
|||
"_island": "{\r\n \u0022file1\u0022: {\r\n \u0022script\u0022: [\r\n {\r\n \u0022path\u0022: \u0022folder1\u0022,\r\n \u0022command\u0022: \u0022add\u0022\r\n }\r\n ]\r\n }\r\n}",
|
||||
"_type": "Newtonsoft.Json.Linq.JObject, Newtonsoft.Json"
|
||||
},
|
||||
"_type": "System.Collections.Generic.Dictionary\u00602[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]], System.Private.CoreLib"
|
||||
"_type": "ObjectDictionary"
|
||||
}
|
||||
|
|
@ -10,5 +10,5 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"_type": "System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]], System.Private.CoreLib"
|
||||
"_type": "ObjectDictionary"
|
||||
}
|
||||
|
|
@ -4,5 +4,5 @@
|
|||
"_island": "[\r\n {\r\n \u0022path\u0022: \u0022folder1\u0022,\r\n \u0022command\u0022: \u0022add\u0022\r\n }\r\n]",
|
||||
"_type": "System.Text.Json.Nodes.JsonArray, System.Text.Json"
|
||||
},
|
||||
"_type": "System.Collections.Generic.Dictionary\u00602[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]], System.Private.CoreLib"
|
||||
"_type": "ObjectDictionary"
|
||||
}
|
||||
|
|
@ -4,5 +4,5 @@
|
|||
"_island": "{\r\n \u0022file1\u0022: {\r\n \u0022script\u0022: [\r\n {\r\n \u0022path\u0022: \u0022folder1\u0022,\r\n \u0022command\u0022: \u0022add\u0022\r\n }\r\n ]\r\n }\r\n}",
|
||||
"_type": "System.Text.Json.Nodes.JsonObject, System.Text.Json"
|
||||
},
|
||||
"_type": "System.Collections.Generic.Dictionary\u00602[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]], System.Private.CoreLib"
|
||||
"_type": "ObjectDictionary"
|
||||
}
|
||||
|
|
@ -10,5 +10,5 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"_type": "System.Collections.Generic.Dictionary\u00602[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]], System.Private.CoreLib"
|
||||
"_type": "ObjectDictionary"
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using System.Dynamic;
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using Elsa.Expressions.Services;
|
||||
using Elsa.Workflows.Serialization.Converters;
|
||||
using Elsa.Workflows.Serialization.ReferenceHandlers;
|
||||
using Xunit;
|
||||
|
|
@ -74,7 +75,6 @@ public class Tests
|
|||
private JsonSerializerOptions GetSerializerOptions()
|
||||
{
|
||||
var referenceHandler = new CrossScopedReferenceHandler();
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
ReferenceHandler = referenceHandler,
|
||||
|
|
@ -85,7 +85,7 @@ public class Tests
|
|||
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
options.Converters.Add(JsonMetadataServices.TimeSpanConverter);
|
||||
options.Converters.Add(new PolymorphicObjectConverterFactory());
|
||||
options.Converters.Add(new PolymorphicObjectConverterFactory(new WellKnownTypeRegistry()));
|
||||
return options;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue