Add ExpandoObjectConverter for JavaScript object conversion
Introduced `ConverterHelper` and `ExpandoObjectConverter` to handle conversions from .NET dynamic objects to JavaScript objects. Updated integration tests to validate the conversion and sorting functionalities. Fixes #5895
This commit is contained in:
parent
0a9d4102c9
commit
71d56e1665
1
Elsa.sln
1
Elsa.sln
|
|
@ -799,6 +799,7 @@ Global
|
|||
{F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
|
|||
59
src/modules/Elsa.JavaScript/Helpers/ConverterHelper.cs
Normal file
59
src/modules/Elsa.JavaScript/Helpers/ConverterHelper.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System.Collections;
|
||||
using Elsa.Extensions;
|
||||
using Jint;
|
||||
using Jint.Native;
|
||||
using Jint.Native.Object;
|
||||
using Jint.Runtime.Descriptors;
|
||||
|
||||
namespace Elsa.JavaScript.Helpers;
|
||||
|
||||
internal static class ConverterHelper
|
||||
{
|
||||
public static ObjectInstance ConvertToJsObject(Engine engine, IDictionary<string, object?> expando)
|
||||
{
|
||||
var jsObject = engine.Intrinsics.Object.Construct([]);
|
||||
|
||||
foreach (var kvp in expando)
|
||||
{
|
||||
var value = kvp.Value;
|
||||
var jsValue = ConvertToJsValue(engine, value);
|
||||
var propertyDescriptor = new PropertyDescriptor(jsValue, true, true, true);
|
||||
jsObject.DefineOwnProperty(kvp.Key, propertyDescriptor);
|
||||
}
|
||||
|
||||
return jsObject;
|
||||
}
|
||||
|
||||
private static JsValue ConvertToJsValue(Engine engine, object? value)
|
||||
{
|
||||
if (value == null)
|
||||
return JsValue.Null;
|
||||
|
||||
if (value is IDictionary<string, object?> dict)
|
||||
return ConvertToJsObject(engine, dict);
|
||||
|
||||
var valueType = value.GetType();
|
||||
if (valueType.IsCollectionType())
|
||||
{
|
||||
var list = (ICollection)value;
|
||||
var jsArray = engine.Intrinsics.Array.Construct(list.Count);
|
||||
var index = 0;
|
||||
|
||||
foreach (var item in list)
|
||||
jsArray.Set(index++, ConvertToJsValue(engine, item), true);
|
||||
|
||||
return jsArray;
|
||||
}
|
||||
|
||||
if (value is string str)
|
||||
return JsValue.FromObject(engine, str);
|
||||
|
||||
if (value is int or double or float or decimal)
|
||||
return JsValue.FromObject(engine, Convert.ToDouble(value));
|
||||
|
||||
if (value is bool b)
|
||||
return JsValue.FromObject(engine, b);
|
||||
|
||||
return JsValue.FromObject(engine, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System.Dynamic;
|
||||
using Elsa.JavaScript.Helpers;
|
||||
using Jint;
|
||||
using Jint.Native;
|
||||
using Jint.Runtime.Interop;
|
||||
|
||||
namespace Elsa.JavaScript.ObjectConverters;
|
||||
|
||||
internal class ExpandoObjectConverter : IObjectConverter
|
||||
{
|
||||
public bool TryConvert(Engine engine, object value, out JsValue result)
|
||||
{
|
||||
if (value is ExpandoObject expandoObject)
|
||||
{
|
||||
result = ConverterHelper.ConvertToJsObject(engine, expandoObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = JsValue.Null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ public class JintJavaScriptEvaluator(IConfiguration configuration, INotification
|
|||
return instance;
|
||||
});
|
||||
|
||||
opts.Interop.ObjectConverters.Add(new ByteArrayConverter());
|
||||
opts.Interop.ObjectConverters.AddRange([new ByteArrayConverter(), new ExpandoObjectConverter()]);
|
||||
});
|
||||
|
||||
configureEngine?.Invoke(engine);
|
||||
|
|
|
|||
|
|
@ -11,40 +11,50 @@ namespace Elsa.Workflows.IntegrationTests.Scenarios.JavaScriptListsAndArrays;
|
|||
|
||||
public class Tests
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IJavaScriptEvaluator _evaluator;
|
||||
private readonly ExpressionExecutionContext _expressionContext;
|
||||
|
||||
public Tests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
var testOutputHelper1 = testOutputHelper ?? throw new ArgumentNullException(nameof(testOutputHelper));
|
||||
_services = new TestApplicationBuilder(testOutputHelper1).Build();
|
||||
_evaluator = _services.GetRequiredService<IJavaScriptEvaluator>();
|
||||
_expressionContext = new ExpressionExecutionContext(_services, new MemoryRegister());
|
||||
var services = new TestApplicationBuilder(testOutputHelper1).Build();
|
||||
_evaluator = services.GetRequiredService<IJavaScriptEvaluator>();
|
||||
_expressionContext = new ExpressionExecutionContext(services, new MemoryRegister());
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Workflow inputs containing .NET lists on dynamic objects are converted to arrays for use in JavaScript.")]
|
||||
public async Task Test1()
|
||||
{
|
||||
dynamic dynamicObject = new ExpandoObject();
|
||||
|
||||
dynamicObject.List = new List<string> { "a", "b", "c" };
|
||||
|
||||
dynamicObject.List = new List<string>
|
||||
{
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
};
|
||||
var script = "getObj().List.filter(x => x === 'b').length === 1";
|
||||
_expressionContext.SetVariable("obj", (object)dynamicObject);
|
||||
var result = await _evaluator.EvaluateAsync(script, typeof(bool), _expressionContext);
|
||||
|
||||
|
||||
Assert.True((bool)result!);
|
||||
}
|
||||
|
||||
|
||||
[Fact(DisplayName = "Can access list properties as arrays")]
|
||||
public async Task Test2()
|
||||
{
|
||||
var person = new ExpandoObject();
|
||||
var magicNumbers = new[]{42, 43, 44};
|
||||
var magicNumbers = new[]
|
||||
{
|
||||
42, 43, 44
|
||||
};
|
||||
var order1 = new ExpandoObject();
|
||||
|
||||
|
||||
order1.TryAdd("magicNumbers", magicNumbers);
|
||||
person.TryAdd("orders", new []{ order1 });
|
||||
person.TryAdd("orders", new[]
|
||||
{
|
||||
order1
|
||||
});
|
||||
person.TryAdd("name", "John");
|
||||
person.TryAdd("age", 12);
|
||||
|
||||
|
|
@ -57,8 +67,11 @@ public class Tests
|
|||
person.TryAdd("languages", languages);
|
||||
|
||||
var obj = new ExpandoObject();
|
||||
obj.TryAdd("persons", new List<object> { person });
|
||||
|
||||
obj.TryAdd("persons", new List<object>
|
||||
{
|
||||
person
|
||||
});
|
||||
|
||||
var name = await _evaluator.EvaluateAsync("o.persons.filter(x => x.age == 12)[0].name", typeof(string), _expressionContext, configureEngine: engine => engine.SetValue("o", obj));
|
||||
var language = await _evaluator.EvaluateAsync("o.persons[0].languages.filter(x => x == 'English')[0]", typeof(string), _expressionContext, configureEngine: engine => engine.SetValue("o", obj));
|
||||
var magicNumber = await _evaluator.EvaluateAsync("o.persons[0].orders[0].magicNumbers[1]", typeof(int), _expressionContext, configureEngine: engine => engine.SetValue("o", obj));
|
||||
|
|
@ -66,4 +79,41 @@ public class Tests
|
|||
Assert.Equal("English", language);
|
||||
Assert.Equal(43, magicNumber);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "Can sort array and list properties as mutable arrays")]
|
||||
[MemberData(nameof(ArraySortEnumerableData))]
|
||||
// See also: https://github.com/sebastienros/jint/issues/1942
|
||||
public async Task Test5(IEnumerable<double> collection)
|
||||
{
|
||||
dynamic dynamicObject = new ExpandoObject();
|
||||
dynamicObject.Items = collection;
|
||||
var script = """
|
||||
const model = getModel();
|
||||
model.Items.sort((a, b) => a - b);
|
||||
return model;
|
||||
""";
|
||||
_expressionContext.SetVariable("Model", (object)dynamicObject);
|
||||
dynamicObject = (ExpandoObject)(await _evaluator.EvaluateAsync(script, typeof(ExpandoObject), _expressionContext))!;
|
||||
var items = ((object[])dynamicObject.Items).Cast<double>().ToArray();
|
||||
|
||||
Assert.Equal([2, 4, 8], items);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ArraySortEnumerableData = new object[][]
|
||||
{
|
||||
[
|
||||
new double[]
|
||||
{
|
||||
8, 4, 2
|
||||
}
|
||||
],
|
||||
[
|
||||
new List<double>
|
||||
{
|
||||
8,
|
||||
4,
|
||||
2
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue