diff --git a/src/modules/Elsa.Email/Activities/SendEmail.cs b/src/modules/Elsa.Email/Activities/SendEmail.cs index 4ec9ca02d..db68773ca 100644 --- a/src/modules/Elsa.Email/Activities/SendEmail.cs +++ b/src/modules/Elsa.Email/Activities/SendEmail.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Dynamic; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; @@ -183,6 +184,22 @@ public class SendEmail : Activity else if (emailAttachment.Content is Stream stream) await bodyBuilder.Attachments.AddAsync(fileName, stream, parsedContentType, cancellationToken); + break; + } + case ExpandoObject expandoObject: + { + var dictionary = new Dictionary(expandoObject, StringComparer.OrdinalIgnoreCase); + var fileName = dictionary.GetValue("FileName") ?? $"Attachment-{++index}"; + var contentType = dictionary.GetValue("ContentType") ?? "application/binary"; + var parsedContentType = ContentType.Parse(contentType); + var content = dictionary.GetValue("Content"); + + if (content is byte[] bytes) + bodyBuilder.Attachments.Add(fileName, bytes, parsedContentType); + + else if (content is Stream stream) + await bodyBuilder.Attachments.AddAsync(fileName, stream, parsedContentType, cancellationToken); + break; } default: diff --git a/src/modules/Elsa.Expressions/Elsa.Expressions.csproj b/src/modules/Elsa.Expressions/Elsa.Expressions.csproj index d696bff63..66d1a6e5e 100644 --- a/src/modules/Elsa.Expressions/Elsa.Expressions.csproj +++ b/src/modules/Elsa.Expressions/Elsa.Expressions.csproj @@ -15,6 +15,7 @@ + diff --git a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs index f59e9861c..899a84ddc 100644 --- a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs +++ b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs @@ -102,15 +102,29 @@ public static class ObjectConverter return jsonElement.Deserialize(targetType, serializerOptions); } - if (value is JsonNode jsonNode and not JsonArray) // If the value is a JsonNode, we can convert it to the target type. If it's a JsonArray, we let the enumerable conversion logic handle it. + if (value is JsonNode jsonNode) { - return underlyingTargetType switch + if (jsonNode is not JsonArray jsonArray) { - { } t when t == typeof(string) => jsonNode.ToString(), - { } t when t == typeof(ExpandoObject) && jsonNode.GetValueKind() == JsonValueKind.Object => JsonSerializer.Deserialize(jsonNode.ToJsonString()), - { } t when t != typeof(object) || converterOptions?.DeserializeJsonObjectToObject == true => jsonNode.Deserialize(targetType, serializerOptions), - _ => jsonNode - }; + return underlyingTargetType switch + { + { } t when t == typeof(string) => jsonNode.ToString(), + { } t when t == typeof(ExpandoObject) && jsonNode.GetValueKind() == JsonValueKind.Object => JsonSerializer.Deserialize(jsonNode.ToJsonString()), + { } t when t != typeof(object) || converterOptions?.DeserializeJsonObjectToObject == true => jsonNode.Deserialize(targetType, serializerOptions), + _ => jsonNode + }; + } + + // Convert to target type if target type is an array or a generic collection. + if (targetType.IsArray || targetType.IsCollectionType()) + { + // The element type of the source array is JsonObject. If the element type of the target array is Object then return the source array as an array of JsonObjects. + // Deserializing normally would return an array of JsonElement instead of JsonObject, but we want to keep JsonObject elements: + var targetElementType = targetType.IsArray ? targetType.GetElementType()! : targetType.GenericTypeArguments[0]; + + if (targetElementType != typeof(object)) + return jsonArray.Deserialize(targetType, serializerOptions); + } } if (underlyingSourceType == typeof(string) && !underlyingTargetType.IsPrimitive && underlyingTargetType != typeof(object)) diff --git a/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Person.cs b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Person.cs new file mode 100644 index 000000000..b5583db8b --- /dev/null +++ b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Person.cs @@ -0,0 +1,9 @@ +namespace Elsa.Workflows.Core.UnitTests.ObjectConversion +{ + public class Person + { + public double? Age { get; set; } + + public string? Name { get; set; } + } +} diff --git a/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs index 6525f5d0e..c245cb5cc 100644 --- a/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs +++ b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs @@ -283,4 +283,21 @@ public class Tests Assert.Equal("Bob", secondElement["name"]?.ToString()); Assert.Equal("25", secondElement["age"]?.ToString()); } + + [Fact] + public void ConvertFrom_JsonArrayToArrayOfComplextType_ReturnsArrayOfComplexType() + { + // Arrange + var jsonArrayString = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]"; + var options = new ObjectConverterOptions(); + + // Act + var result = jsonArrayString.ConvertTo(options); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Length); + Assert.Equal("Alice", result[0].Name); + Assert.Equal("Bob", result[1].Name); + } } \ No newline at end of file