Add synchronous serialization methods and mark async as obsolete

Implemented synchronous methods for serialization and deserialization while marking the asynchronous methods as obsolete across various storage and serialization services. This includes updates to handle serialization within the Save and Load methods, enhancing performance by avoiding unnecessary Task usage.
This commit is contained in:
Sipke Schoorstra 2024-09-27 12:59:59 +02:00
parent 7609b0e7ca
commit 2e1ed995da
13 changed files with 264 additions and 79 deletions

View file

@ -0,0 +1,41 @@
using Elsa.Expressions.Models;
using Elsa.Extensions;
using Elsa.Scheduling.Activities;
using Elsa.Workflows;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Contracts;
namespace Elsa.Server.Web;
public class SampleWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
// The WithVariable method ensures that the created variable will be added to the Workflow's Variables collection, which is required for persistent variables.
var variable1 = workflow.WithVariable<string>("Foo").WithWorkflowStorage();
workflow.Variables =
[
variable1
];
workflow.Root = new Sequence
{
Activities =
{
new StartAt(DateTimeOffset.UtcNow + TimeSpan.FromSeconds(5))
{
CanStartWorkflow = true
},
new WriteLine(variable1),
new SetVariable
{
Variable = variable1,
Value = new (Literal.From("Bar"))
},
new Delay(TimeSpan.FromSeconds(1)),
new WriteLine(variable1)
}
};
}
}

View file

@ -1,7 +1,7 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"OpenTelemetry": "Debug"

View file

@ -38,14 +38,14 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore
/// <inheritdoc />
public async Task SaveAsync(ActivityExecutionRecord record, CancellationToken cancellationToken = default)
{
var mappedRecord = await Map(record, cancellationToken);
var mappedRecord = Map(record, cancellationToken);
await _store.SaveAsync(mappedRecord, PrimaryKeyName, cancellationToken);
}
/// <inheritdoc />
public async Task SaveManyAsync(IEnumerable<ActivityExecutionRecord> records, CancellationToken cancellationToken = default)
{
var mappedRecords = await Task.WhenAll(records.Select(async x => await Map(x, cancellationToken)));
var mappedRecords = records.Select(x => Map(x, cancellationToken));
await _store.SaveManyAsync(mappedRecords, PrimaryKeyName, cancellationToken);
}
@ -53,21 +53,21 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore
public async Task<ActivityExecutionRecord?> FindAsync(ActivityExecutionRecordFilter filter, CancellationToken cancellationToken = default)
{
var record = await _store.FindAsync(q => ApplyFilter(q, filter), cancellationToken);
return record == null ? null : await MapAsync(record, cancellationToken);
return record == null ? null : Map(record, cancellationToken);
}
/// <inheritdoc />
public async Task<IEnumerable<ActivityExecutionRecord>> FindManyAsync<TOrderBy>(ActivityExecutionRecordFilter filter, ActivityExecutionRecordOrder<TOrderBy> order, CancellationToken cancellationToken = default)
{
var records = await _store.FindManyAsync(q => ApplyFilter(q, filter), order.KeySelector.GetPropertyName(), order.Direction, cancellationToken);
return await Task.WhenAll(records.Select(async x => await MapAsync(x, cancellationToken)));
return records.Select( x => Map(x, cancellationToken)).ToList();
}
/// <inheritdoc />
public async Task<IEnumerable<ActivityExecutionRecord>> FindManyAsync(ActivityExecutionRecordFilter filter, CancellationToken cancellationToken = default)
{
var records = await _store.FindManyAsync(q => ApplyFilter(q, filter), cancellationToken);
return await Task.WhenAll(records.Select(async x => await MapAsync(x, cancellationToken)));
return records.Select( x => Map(x, cancellationToken)).ToList();
}
/// <inheritdoc />
@ -115,7 +115,7 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore
}
}
private async ValueTask<ActivityExecutionRecordRecord> Map(ActivityExecutionRecord source, CancellationToken cancellationToken)
private ActivityExecutionRecordRecord Map(ActivityExecutionRecord source, CancellationToken cancellationToken)
{
return new ActivityExecutionRecordRecord
{
@ -130,15 +130,15 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore
HasBookmarks = source.HasBookmarks,
Status = source.Status.ToString(),
ActivityTypeVersion = source.ActivityTypeVersion,
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,
SerializedActivityState = source.ActivityState != null ? _safeSerializer.Serialize(source.ActivityState, cancellationToken) : null,
SerializedPayload = source.Payload != null ? _safeSerializer.Serialize(source.Payload, cancellationToken) : null,
SerializedOutputs = source.Outputs?.Any() == true ? _safeSerializer.Serialize(source.Outputs, cancellationToken) : null,
SerializedException = source.Exception != null ? _payloadSerializer.Serialize(source.Exception) : null,
SerializedProperties = source.Properties.Any() ? await _safeSerializer.SerializeAsync(source.Properties, cancellationToken) : null
SerializedProperties = source.Properties.Any() ? _safeSerializer.Serialize(source.Properties, cancellationToken) : null
};
}
private async ValueTask<ActivityExecutionRecord> MapAsync(ActivityExecutionRecordRecord source, CancellationToken cancellationToken)
private ActivityExecutionRecord Map(ActivityExecutionRecordRecord source, CancellationToken cancellationToken)
{
return new ActivityExecutionRecord
{
@ -153,11 +153,11 @@ public class DapperActivityExecutionRecordStore : IActivityExecutionStore
HasBookmarks = source.HasBookmarks,
Status = Enum.Parse<ActivityStatus>(source.Status),
ActivityTypeVersion = source.ActivityTypeVersion,
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,
Properties = source.SerializedProperties != null ? await _safeSerializer.DeserializeAsync<IDictionary<string, object>>(source.SerializedProperties, cancellationToken) : default
ActivityState = source.SerializedActivityState != null ? _payloadSerializer.Deserialize<IDictionary<string, object>>(source.SerializedActivityState) : null,
Payload = source.SerializedPayload != null ? _safeSerializer.Deserialize<IDictionary<string, object>>(source.SerializedPayload, cancellationToken) : null,
Outputs = source.SerializedOutputs != null ? _safeSerializer.Deserialize<IDictionary<string, object?>>(source.SerializedOutputs, cancellationToken) : null,
Exception = source.SerializedException != null ? _payloadSerializer.Deserialize<ExceptionState>(source.SerializedException) : null,
Properties = source.SerializedProperties != null ? _safeSerializer.Deserialize<IDictionary<string, object>>(source.SerializedProperties, cancellationToken) : null
};
}

View file

@ -83,12 +83,12 @@ public class EFCoreActivityExecutionStore(
{
entity = entity.SanitizeLogMessage();
var compressionAlgorithm = options.Value.CompressionAlgorithm ?? nameof(None);
var serializedActivityState = entity.ActivityState != null ? await safeSerializer.SerializeAsync(entity.ActivityState, cancellationToken) : null;
var serializedActivityState = entity.ActivityState != null ? safeSerializer.Serialize(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?.Any() == true ? await safeSerializer.SerializeAsync(entity.Outputs, cancellationToken) : null;
dbContext.Entry(entity).Property("SerializedOutputs").CurrentValue = entity.Outputs?.Any() == true ? safeSerializer.Serialize(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;

View file

@ -87,8 +87,8 @@ 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?.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;
dbContext.Entry(entity).Property("SerializedActivityState").CurrentValue = entity.ActivityState?.Any() == true ? _safeSerializer.Serialize(entity.ActivityState, cancellationToken) : null;
dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = entity.Payload != null ? _safeSerializer.Serialize(entity.Payload, cancellationToken) : null;
}
private async ValueTask OnLoadAsync(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord? entity, CancellationToken cancellationToken)

View file

@ -121,10 +121,10 @@ internal class Export : ElsaEndpointWithMapper<Request, WorkflowInstanceMapper>
var executionLogRecords = request.IncludeWorkflowExecutionLog ? await LoadWorkflowExecutionLogRecordsAsync(workflowState.Id, cancellationToken) : default;
var activityExecutionLogRecords = request.IncludeActivityExecutionLog ? await LoadActivityExecutionLogRecordsAsync(workflowState.Id, cancellationToken) : default;
var bookmarks = request.IncludeBookmarks ? await LoadBookmarksAsync(workflowState.Id, cancellationToken) : null;
var workflowStateElement = await _workflowStateSerializer.SerializeToElementAsync(workflowState, cancellationToken);
var workflowStateElement = _workflowStateSerializer.SerializeToElement(workflowState, cancellationToken);
var bookmarksElement = bookmarks != null ? SerializeBookmarks(bookmarks) : default(JsonElement?);
var executionLogRecordsElement = executionLogRecords != null ? await _safeSerializer.SerializeToElementAsync(executionLogRecords, cancellationToken) : default(JsonElement?);
var activityExecutionLogRecordsElement = activityExecutionLogRecords != null ? await _safeSerializer.SerializeToElementAsync(activityExecutionLogRecords, cancellationToken) : default(JsonElement?);
var executionLogRecordsElement = executionLogRecords != null ? _safeSerializer.SerializeToElement(executionLogRecords, cancellationToken) : default(JsonElement?);
var activityExecutionLogRecordsElement = activityExecutionLogRecords != null ? _safeSerializer.SerializeToElement(activityExecutionLogRecords, cancellationToken) : default(JsonElement?);
var model = new ExportedWorkflowState(workflowStateElement, bookmarksElement, activityExecutionLogRecordsElement, executionLogRecordsElement);
return model;
}

View file

@ -137,13 +137,13 @@ internal class Import : ElsaEndpointWithoutRequest<Response>
if (model.ActivityExecutionRecords != null)
{
var activityExecutionRecords = await _safeSerializer.DeserializeAsync<ICollection<ActivityExecutionRecord>>(model.ActivityExecutionRecords.Value, cancellationToken);
var activityExecutionRecords = _safeSerializer.Deserialize<ICollection<ActivityExecutionRecord>>(model.ActivityExecutionRecords.Value, cancellationToken);
await _activityExecutionStore.SaveManyAsync(activityExecutionRecords, cancellationToken);
}
if (model.WorkflowExecutionLogRecords != null)
{
var workflowExecutionLogRecords = await _safeSerializer.DeserializeAsync<ICollection<WorkflowExecutionLogRecord>>(model.WorkflowExecutionLogRecords.Value, cancellationToken);
var workflowExecutionLogRecords = _safeSerializer.Deserialize<ICollection<WorkflowExecutionLogRecord>>(model.WorkflowExecutionLogRecords.Value, cancellationToken);
await _workflowExecutionLogStore.SaveManyAsync(workflowExecutionLogRecords, cancellationToken);
}
}

View file

@ -11,24 +11,52 @@ public interface ISafeSerializer
/// <summary>
/// Serializes the specified state.
/// </summary>
[Obsolete("Use the non-async Serialize instead.")]
[RequiresUnreferencedCode("The type T may be trimmed.")]
ValueTask<string> SerializeAsync(object? value, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified state to a <see cref="JsonElement"/> object.
/// </summary>
[Obsolete("Use the non-async SerializeToElement instead.")]
[RequiresUnreferencedCode("The type T may be trimmed.")]
ValueTask<JsonElement> SerializeToElementAsync(object? value, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified state.
/// </summary>
[Obsolete("Use the non-async Deserialize instead.")]
[RequiresUnreferencedCode("The type T may be trimmed.")]
ValueTask<T> DeserializeAsync<T>(string json, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified state.
/// </summary>
[Obsolete("Use the non-async Deserialize instead.")]
[RequiresUnreferencedCode("The type T may be trimmed.")]
ValueTask<T> DeserializeAsync<T>(JsonElement element, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified state.
/// </summary>
[RequiresUnreferencedCode("The type T may be trimmed.")]
string Serialize(object? value, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified state to a <see cref="JsonElement"/> object.
/// </summary>
[RequiresUnreferencedCode("The type T may be trimmed.")]
JsonElement SerializeToElement(object? value, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified state.
/// </summary>
[RequiresUnreferencedCode("The type T may be trimmed.")]
T Deserialize<T>(string json, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified state.
/// </summary>
[RequiresUnreferencedCode("The type T may be trimmed.")]
T Deserialize<T>(JsonElement element, CancellationToken cancellationToken = default);
}

View file

@ -16,8 +16,9 @@ public interface IWorkflowStateSerializer
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The serialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[Obsolete("Use the non-async version Serialize instead.")]
Task<string> SerializeAsync(WorkflowState workflowState, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified workflow state.
/// </summary>
@ -25,8 +26,18 @@ public interface IWorkflowStateSerializer
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The serialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
string Serialize(WorkflowState workflowState, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified workflow state.
/// </summary>
/// <param name="workflowState">The workflow state to serialize.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The serialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[Obsolete("Use the non-async version SerializeToUtfBytes instead.")]
Task<byte[]> SerializeToUtfBytesAsync(WorkflowState workflowState, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified workflow state.
/// </summary>
@ -34,8 +45,18 @@ public interface IWorkflowStateSerializer
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The serialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
byte[] SerializeToUtfBytes(WorkflowState workflowState, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified workflow state.
/// </summary>
/// <param name="workflowState">The workflow state to serialize.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The serialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[Obsolete("Use the non-async version SerializeToElement instead.")]
Task<JsonElement> SerializeToElementAsync(WorkflowState workflowState, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified workflow state.
/// </summary>
@ -43,32 +64,81 @@ public interface IWorkflowStateSerializer
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The serialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
JsonElement SerializeToElement(WorkflowState workflowState, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified workflow state.
/// </summary>
/// <param name="workflowState">The workflow state to serialize.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The serialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[Obsolete("Use the non-async version Serialize instead.")]
Task<string> SerializeAsync(object workflowState, CancellationToken cancellationToken = default);
/// <summary>
/// Serializes the specified workflow state.
/// </summary>
/// <param name="workflowState">The workflow state to serialize.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The serialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
string Serialize(object workflowState, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified serialized state.
/// </summary>
/// <param name="serializedState">The serialized state.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The deserialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
[Obsolete("Use the non-async version Deserialize instead.")]
Task<WorkflowState> DeserializeAsync(string serializedState, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified serialized state.
/// </summary>
/// <param name="serializedState">The serialized state.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The deserialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
WorkflowState Deserialize(string serializedState, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified serialized state.
/// </summary>
/// <param name="serializedState">The serialized state.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The deserialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
[Obsolete("Use the non-async version Deserialize instead.")]
Task<WorkflowState> DeserializeAsync(JsonElement serializedState, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified serialized state.
/// </summary>
/// <param name="serializedState">The serialized state.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The deserialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
WorkflowState Deserialize(JsonElement serializedState, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified serialized state.
/// </summary>
/// <param name="serializedState">The serialized state.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The deserialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
[Obsolete("Use the non-async version Deserialize instead.")]
Task<T> DeserializeAsync<T>(string serializedState, CancellationToken cancellationToken = default);
/// <summary>
/// Deserializes the specified serialized state.
/// </summary>
/// <param name="serializedState">The serialized state.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The deserialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
T Deserialize<T>(string serializedState, CancellationToken cancellationToken = default);
}

View file

@ -253,7 +253,7 @@ public static class ActivityExecutionContextExtensions
// Serializing the value ensures we store a copy of the value and not a reference to the input, which may change over time.
if (inputDescriptor.IsSerializable != false)
{
var serializedValue = await context.GetRequiredService<ISafeSerializer>().SerializeToElementAsync(value);
var serializedValue = context.GetRequiredService<ISafeSerializer>().SerializeToElement(value);
context.ActivityState[inputDescriptor.Name] = serializedValue;
}
@ -409,7 +409,7 @@ public static class ActivityExecutionContextExtensions
if (outputValue == null!)
continue;
var serializedOutputValue = await serializer.SerializeAsync(outputValue, cancellationToken);
var serializedOutputValue = serializer.Serialize(outputValue, cancellationToken);
context.JournalData[outputName] = serializedOutputValue;
}

View file

@ -31,62 +31,100 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat
/// <inheritdoc />
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[Obsolete("Use the non-async version Serialize instead.")]
public Task<string> SerializeAsync(WorkflowState workflowState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return Task.FromResult(JsonSerializer.Serialize(workflowState, options));
return Task.FromResult(Serialize(workflowState, cancellationToken));
}
/// <inheritdoc />
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[Obsolete("Use the non-async version SerializeToUtfBytes instead.")]
public Task<byte[]> SerializeToUtfBytesAsync(WorkflowState workflowState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return Task.FromResult(JsonSerializer.SerializeToUtf8Bytes(workflowState, options));
return Task.FromResult(SerializeToUtfBytes(workflowState, cancellationToken));
}
/// <inheritdoc />
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[Obsolete("Use the non-async version SerializeToElement instead.")]
public Task<JsonElement> SerializeToElementAsync(WorkflowState workflowState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return Task.FromResult(JsonSerializer.SerializeToElement(workflowState, options));
return Task.FromResult(SerializeToElement(workflowState, cancellationToken));
}
/// <inheritdoc />
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The serialization process may require access to the type.")]
[Obsolete("Use the non-async version Serialize instead.")]
public Task<string> SerializeAsync(object workflowState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
var json = JsonSerializer.Serialize(workflowState, workflowState.GetType(), options);
return Task.FromResult(json);
return Task.FromResult(Serialize(workflowState, cancellationToken));
}
/// <inheritdoc />
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
[Obsolete("Use the non-async version Deserialize instead.")]
public Task<WorkflowState> DeserializeAsync(string serializedState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
var workflowState = JsonSerializer.Deserialize<WorkflowState>(serializedState, options)!;
return Task.FromResult(workflowState);
return Task.FromResult(Deserialize(serializedState, cancellationToken));
}
/// <inheritdoc />
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
[Obsolete("Use the non-async version Deserialize instead.")]
public Task<WorkflowState> DeserializeAsync(JsonElement serializedState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
var workflowState = serializedState.Deserialize<WorkflowState>(options)!;
return Task.FromResult(workflowState);
return Task.FromResult(Deserialize(serializedState, cancellationToken));
}
/// <inheritdoc />
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
[Obsolete("Use the non-async version Deserialize instead.")]
public Task<T> DeserializeAsync<T>(string serializedState, CancellationToken cancellationToken = default)
{
return Task.FromResult(Deserialize<T>(serializedState, cancellationToken));
}
public string Serialize(WorkflowState workflowState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
var workflowState = JsonSerializer.Deserialize<T>(serializedState, options)!;
return Task.FromResult(workflowState);
return JsonSerializer.Serialize(workflowState, options);
}
public byte[] SerializeToUtfBytes(WorkflowState workflowState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return JsonSerializer.SerializeToUtf8Bytes(workflowState, options);
}
public JsonElement SerializeToElement(WorkflowState workflowState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return JsonSerializer.SerializeToElement(workflowState, options);
}
public string Serialize(object workflowState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return JsonSerializer.Serialize(workflowState, workflowState.GetType(), options);
}
public WorkflowState Deserialize(string serializedState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return JsonSerializer.Deserialize<WorkflowState>(serializedState, options)!;
}
public WorkflowState Deserialize(JsonElement serializedState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return serializedState.Deserialize<WorkflowState>(options)!;
}
public T Deserialize<T>(string serializedState, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return JsonSerializer.Deserialize<T>(serializedState, options)!;
}
/// <inheritdoc />

View file

@ -22,39 +22,59 @@ public class SafeSerializer : ConfigurableSerializer, ISafeSerializer
[RequiresUnreferencedCode("The type T may be trimmed.")]
public ValueTask<string> SerializeAsync(object? value, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return ValueTask.FromResult(JsonSerializer.Serialize(value, options));
return ValueTask.FromResult(Serialize(value, cancellationToken));
}
/// <inheritdoc />
[RequiresUnreferencedCode("The type T may be trimmed.")]
public ValueTask<JsonElement> SerializeToElementAsync(object? value, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return new(JsonSerializer.SerializeToElement(value, options));
return new(SerializeToElement(value, cancellationToken));
}
/// <inheritdoc />
[RequiresUnreferencedCode("The type T may be trimmed.")]
public ValueTask<T> DeserializeAsync<T>(string json, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return new(JsonSerializer.Deserialize<T>(json, options)!);
return new(Deserialize<T>(json, cancellationToken));
}
/// <inheritdoc />
[RequiresUnreferencedCode("The type T may be trimmed.")]
public ValueTask<T> DeserializeAsync<T>(JsonElement element, CancellationToken cancellationToken = default)
{
return new(Deserialize<T>(element, cancellationToken));
}
public string Serialize(object? value, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return new(element.Deserialize<T>(options)!);
return JsonSerializer.Serialize(value, options);
}
public JsonElement SerializeToElement(object? value, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return JsonSerializer.SerializeToElement(value, options);
}
public T Deserialize<T>(string json, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return JsonSerializer.Deserialize<T>(json, options)!;
}
public T Deserialize<T>(JsonElement element, CancellationToken cancellationToken = default)
{
var options = GetOptions();
return element.Deserialize<T>(options)!;
}
/// <inheritdoc />
protected override void AddConverters(JsonSerializerOptions options)
{
var expressionDescriptorRegistry = ServiceProvider.GetRequiredService<IExpressionDescriptorRegistry>();
options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
options.Converters.Add(new TypeJsonConverter(WellKnownTypeRegistry.CreateDefault()));
options.Converters.Add(new SafeValueConverterFactory());

View file

@ -32,17 +32,17 @@ public class SerializerUnicodeEncodingTests(ITestOutputHelper testOutputHelper)
}
[Fact]
public async Task TestSafeSerializer()
public void TestSafeSerializer()
{
var serializer = _serviceProvider.GetRequiredService<ISafeSerializer>();
await TestSerializerAsync(input => serializer.SerializeAsync(input).AsTask());
TestSerializer(input => serializer.Serialize(input));
}
[Fact]
public async Task TestWorkflowStateSerializer()
public void TestWorkflowStateSerializer()
{
var serializer = _serviceProvider.GetRequiredService<IWorkflowStateSerializer>();
await TestSerializerAsync(input => serializer.SerializeAsync(input));
TestSerializer(input => serializer.Serialize(input));
}
private void TestSerializer(Func<object, string> serialize)
@ -56,18 +56,6 @@ public class SerializerUnicodeEncodingTests(ITestOutputHelper testOutputHelper)
var serializedStringValue = GetSerializedTextValue(serializedJson);
Assert.Equal(unicodeString, serializedStringValue);
}
private async Task TestSerializerAsync(Func<object, Task<string>> serialize)
{
var unicodeString = UnicodeRangeGenerator.GenerateUnicodeString();
var anonymousObject = new
{
Text = unicodeString
};
var serializedJson = await serialize(anonymousObject);
var serializedStringValue = GetSerializedTextValue(serializedJson);
Assert.Equal(unicodeString, serializedStringValue);
}
private string GetSerializedTextValue(string serializedJson)
{