diff --git a/Directory.Packages.props b/Directory.Packages.props
index f070fd1cd..d3e0f5472 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -177,11 +177,12 @@
-
-
+
+
+
\ No newline at end of file
diff --git a/build/_build.csproj b/build/_build.csproj
index 987e45789..4617e6f76 100644
--- a/build/_build.csproj
+++ b/build/_build.csproj
@@ -19,16 +19,16 @@
-
+
-
+
diff --git a/src/modules/Elsa.MongoDb/Features/MongoDbFeature.cs b/src/modules/Elsa.MongoDb/Features/MongoDbFeature.cs
index 7d01f8d65..a4408e13c 100644
--- a/src/modules/Elsa.MongoDb/Features/MongoDbFeature.cs
+++ b/src/modules/Elsa.MongoDb/Features/MongoDbFeature.cs
@@ -1,4 +1,5 @@
using System.Text.Json;
+using System.Text.Json.Nodes;
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Elsa.KeyValues.Entities;
@@ -8,6 +9,7 @@ using Elsa.MongoDb.Options;
using Elsa.MongoDb.Serializers;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Runtime.Entities;
+using Elsa.Workflows.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@@ -67,6 +69,7 @@ public class MongoDbFeature : FeatureBase
TryRegisterSerializerOrSkipWhenExist(typeof(Variable), new VariableSerializer());
TryRegisterSerializerOrSkipWhenExist(typeof(Version), new VersionSerializer());
TryRegisterSerializerOrSkipWhenExist(typeof(JsonElement), new JsonElementSerializer());
+ TryRegisterSerializerOrSkipWhenExist(typeof(JsonNode), new JsonNodeBsonConverter());
}
private static void RegisterClassMaps()
diff --git a/src/modules/Elsa.MongoDb/Serializers/JsonNodeBsonSerializer.cs b/src/modules/Elsa.MongoDb/Serializers/JsonNodeBsonSerializer.cs
new file mode 100644
index 000000000..51d793298
--- /dev/null
+++ b/src/modules/Elsa.MongoDb/Serializers/JsonNodeBsonSerializer.cs
@@ -0,0 +1,135 @@
+using System.Text.Json.Nodes;
+using MongoDB.Bson;
+using MongoDB.Bson.IO;
+using MongoDB.Bson.Serialization;
+
+namespace Elsa.MongoDb.Serializers;
+
+///
+/// Serializes a .
+///
+public class JsonNodeBsonConverter : IBsonSerializer
+{
+ ///
+ public Type ValueType => typeof(JsonNode);
+
+ ///
+ public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, JsonNode value)
+ {
+ if (value == null!)
+ {
+ context.Writer.WriteNull();
+ return;
+ }
+
+ context.Writer.WriteStartDocument();
+ context.Writer.WriteName("type");
+
+ switch (value)
+ {
+ case JsonObject jsonObject:
+ context.Writer.WriteString("JsonObject");
+ context.Writer.WriteName("value");
+ context.Writer.WriteString(jsonObject.ToJsonString());
+ break;
+
+ case JsonArray jsonArray:
+ context.Writer.WriteString("JsonArray");
+ context.Writer.WriteName("value");
+ context.Writer.WriteString(jsonArray.ToJsonString());
+ break;
+
+ case JsonValue jsonValue:
+ context.Writer.WriteString("JsonValue");
+ context.Writer.WriteName("value");
+ if (jsonValue.TryGetValue(out string? stringValue))
+ context.Writer.WriteString(stringValue);
+ else if (jsonValue.TryGetValue(out int intValue))
+ context.Writer.WriteInt32(intValue);
+ else if (jsonValue.TryGetValue(out int longValue))
+ context.Writer.WriteInt64(longValue);
+ else if (jsonValue.TryGetValue(out double doubleValue))
+ context.Writer.WriteDouble(doubleValue);
+ else if (jsonValue.TryGetValue(out bool boolValue))
+ context.Writer.WriteBoolean(boolValue);
+ else if (jsonValue.TryGetValue(out DateTimeOffset dateTimeOffsetValue))
+ context.Writer.WriteDateTime(dateTimeOffsetValue.ToUnixTimeMilliseconds());
+ else if (jsonValue.TryGetValue(out DateTime dateTimeValue))
+ context.Writer.WriteDateTime(new DateTimeOffset(dateTimeValue).ToUnixTimeMilliseconds());
+ else
+ throw new BsonSerializationException("Unsupported JsonValue type");
+ break;
+
+ default:
+ throw new BsonSerializationException($"Unexpected JsonNode type: {value.GetType()}");
+ }
+
+ context.Writer.WriteEndDocument();
+ }
+
+ public JsonNode Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
+ {
+ context.Reader.ReadStartDocument();
+ var type = context.Reader.ReadString();
+ context.Reader.ReadName(Utf8NameDecoder.Instance);
+
+ JsonNode result;
+ switch (type)
+ {
+ case "JsonObject":
+ var jsonObjectString = context.Reader.ReadString();
+ result = JsonNode.Parse(jsonObjectString);
+ break;
+
+ case "JsonArray":
+ var jsonArrayString = context.Reader.ReadString();
+ result = JsonNode.Parse(jsonArrayString);
+ break;
+
+ case "JsonValue":
+ var bsonType = context.Reader.GetCurrentBsonType();
+ switch (bsonType)
+ {
+ case BsonType.String:
+ result = JsonValue.Create(context.Reader.ReadString());
+ break;
+ case BsonType.Int32:
+ result = JsonValue.Create(context.Reader.ReadInt32());
+ break;
+ case BsonType.Int64:
+ result = JsonValue.Create(context.Reader.ReadInt64());
+ break;
+ case BsonType.Double:
+ result = JsonValue.Create(context.Reader.ReadDouble());
+ break;
+ case BsonType.Boolean:
+ result = JsonValue.Create(context.Reader.ReadBoolean());
+ break;
+ case BsonType.DateTime:
+ result = JsonValue.Create(context.Reader.ReadDateTime());
+ break;
+ default:
+ throw new BsonSerializationException($"Unsupported BSON type: {bsonType}");
+ }
+ break;
+
+ default:
+ throw new BsonSerializationException($"Unsupported JsonNode type: {type}");
+ }
+
+ context.Reader.ReadEndDocument();
+ return result!;
+ }
+
+ ///
+ public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
+ {
+ Serialize(context, args, (JsonNode)value);
+ }
+
+ object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
+ {
+ return Deserialize(context, args);
+ }
+}
+
diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs
index d3d04873b..eedd35128 100644
--- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs
+++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs
@@ -72,7 +72,7 @@ internal class Export : ElsaEndpoint
foreach (var definition in definitions)
{
var model = await CreateWorkflowModelAsync(definition, cancellationToken);
- var binaryJson = SerializeWorkflowDefinition(model);
+ var binaryJson = await SerializeWorkflowDefinitionAsync(model, cancellationToken);
var fileName = GetFileName(model);
var entry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);
await using var entryStream = entry.Open();
@@ -101,7 +101,7 @@ internal class Export : ElsaEndpoint
}
var model = await CreateWorkflowModelAsync(definition, cancellationToken);
- var binaryJson = SerializeWorkflowDefinition(model);
+ var binaryJson = await SerializeWorkflowDefinitionAsync(model, cancellationToken);
var fileName = GetFileName(model);
await SendBytesAsync(binaryJson, fileName, cancellation: cancellationToken);
@@ -115,10 +115,25 @@ internal class Export : ElsaEndpoint
return fileName;
}
- private byte[] SerializeWorkflowDefinition(WorkflowDefinitionModel model)
+ private async Task SerializeWorkflowDefinitionAsync(WorkflowDefinitionModel model, CancellationToken cancellationToken)
{
- JsonSerializerOptions serializerOptions = _serializer.GetOptions();
- var binaryJson = JsonSerializer.SerializeToUtf8Bytes(model, serializerOptions);
+ var serializerOptions = _serializer.GetOptions();
+ var document = JsonSerializer.SerializeToDocument(model, serializerOptions);
+ var rootElement = document.RootElement;
+
+ using var output = new MemoryStream();
+ await using var writer = new Utf8JsonWriter(output);
+
+ writer.WriteStartObject();
+ writer.WriteString("$schema", "https://elsaworkflows.io/schemas/workflow-definition/v3.0.0/schema.json");
+
+ foreach (var property in rootElement.EnumerateObject())
+ property.WriteTo(writer);
+
+ writer.WriteEndObject();
+
+ await writer.FlushAsync(cancellationToken);
+ var binaryJson = output.ToArray();
return binaryJson;
}
diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Configurators/AdditionalConvertersConfigurator.cs b/src/modules/Elsa.Workflows.Core/Serialization/Configurators/AdditionalConvertersConfigurator.cs
index 6a5404312..da96c7e30 100644
--- a/src/modules/Elsa.Workflows.Core/Serialization/Configurators/AdditionalConvertersConfigurator.cs
+++ b/src/modules/Elsa.Workflows.Core/Serialization/Configurators/AdditionalConvertersConfigurator.cs
@@ -1,6 +1,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Workflows.Serialization.Converters;
+using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.Serialization.Configurators;
@@ -8,23 +9,14 @@ namespace Elsa.Workflows.Serialization.Configurators;
///
/// Add additional objects.
///
-public class AdditionalConvertersConfigurator : SerializationOptionsConfiguratorBase
+[UsedImplicitly]
+public class AdditionalConvertersConfigurator(IServiceProvider serviceProvider) : SerializationOptionsConfiguratorBase
{
- private readonly IServiceProvider _serviceProvider;
-
- ///
- /// Initializes a new instance of the class.
- ///
- public AdditionalConvertersConfigurator(IServiceProvider serviceProvider)
- {
- _serviceProvider = serviceProvider;
- }
-
///
public override void Configure(JsonSerializerOptions options)
{
options.Converters.Add(Create());
}
- private T Create() => ActivatorUtilities.CreateInstance(_serviceProvider);
+ private T Create() => ActivatorUtilities.CreateInstance(serviceProvider);
}
\ No newline at end of file