Follow-up #761 - Fix for recursive case
Unfortunately Jint still chokes in this case if the expando contains further nested expandos. It turns out that I need to do this recursively for it to always work. We also have trouble with IEnumerable types. They also can't be round-tripped from/to JSON when they aren't deserialized as a very specific type.
This commit is contained in:
parent
7ed875e53c
commit
4b5d86135d
|
|
@ -3,6 +3,7 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Scripting.JavaScript.Converters;
|
||||
|
|
@ -50,9 +51,12 @@ namespace Elsa.Scripting.JavaScript.Services
|
|||
if (converter.CanConvertTo(returnType))
|
||||
converter.ConvertTo(returnValue, returnType);
|
||||
|
||||
returnType = (returnValue is ExpandoObject && returnType == typeof(object))? typeof(Dictionary<string,object>) : returnType;
|
||||
if (returnValue is IEnumerable)
|
||||
if(returnValue is ExpandoObject expando && returnType == typeof(object))
|
||||
return RecursivelyPrepareExpandoObjectForReturn(expando);
|
||||
|
||||
if (returnValue is IEnumerable && !(returnValue is string))
|
||||
{
|
||||
returnType = (returnType == typeof(object))? typeof(object[]) : returnType;
|
||||
var json = JsonConvert.SerializeObject(returnValue);
|
||||
return JsonConvert.DeserializeObject(json, returnType);
|
||||
}
|
||||
|
|
@ -62,6 +66,38 @@ namespace Elsa.Scripting.JavaScript.Services
|
|||
|
||||
return Convert.ChangeType(returnValue, returnType);
|
||||
}
|
||||
|
||||
static object? RecursivelyPrepareExpandoObjectForReturn(ExpandoObject obj)
|
||||
{
|
||||
IDictionary<string,object?> ExpandoToDictionary(ExpandoObject expando)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(expando);
|
||||
return (IDictionary<string,object?>) JsonConvert.DeserializeObject(json, typeof(Dictionary<string,object?>))!;
|
||||
}
|
||||
|
||||
object? EnumerableToObject(IEnumerable enumerable)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(enumerable);
|
||||
return JsonConvert.DeserializeObject(json, typeof(object[]));
|
||||
}
|
||||
|
||||
var expandoDictionary = obj as IDictionary<string,object>;
|
||||
|
||||
var allValuesToReplace = (from kvp in expandoDictionary
|
||||
where kvp.Value is IEnumerable && !(kvp.Value is string)
|
||||
let val = (IEnumerable) kvp.Value
|
||||
let replacementValue = (val is ExpandoObject expando)
|
||||
? RecursivelyPrepareExpandoObjectForReturn(expando)
|
||||
: EnumerableToObject(val)
|
||||
select new { Key = kvp.Key, Value = replacementValue })
|
||||
.ToDictionary(k => k.Key, v => v.Value);
|
||||
|
||||
var output = ExpandoToDictionary(obj);
|
||||
foreach(var kvp in allValuesToReplace)
|
||||
output[kvp.Key] = kvp.Value;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private void ConfigureJintEngine(Jint.Options options)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,6 +34,66 @@ namespace Elsa.Scripting.JavaScript.Services
|
|||
Assert.Equal(@"{""foo"":""bar""}", result);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The EvaluateAsync method should be able to roundtrip a JSON object (which contains an embedded object) via JSON.parse and then JSON.stringify"), AutoMoqData]
|
||||
public async Task EvaluateAsyncShouldBeAbleToRoundtripAJsonStringWithAnEmbeddedObjectViaJsonStringify([Frozen] IMediator mediator,
|
||||
[Frozen] IOptions<ScriptOptions> options,
|
||||
JavaScriptService sut,
|
||||
[StubActivityExecutionContext] ActivityExecutionContext context1,
|
||||
[StubActivityExecutionContext] ActivityExecutionContext context2)
|
||||
{
|
||||
object returnedValue = null;
|
||||
Mock.Get(mediator)
|
||||
.Setup(x => x.Publish(It.Is<EvaluatingJavaScriptExpression>(e => e.ActivityExecutionContext == context2), It.IsAny<CancellationToken>()))
|
||||
.Callback((EvaluatingJavaScriptExpression expression, CancellationToken t) => {
|
||||
expression.Engine.SetValue("MyVariable", returnedValue);
|
||||
});
|
||||
|
||||
returnedValue = await sut.EvaluateAsync(@"JSON.parse(""{\""foo\"":\""bar\"", \""child\"":{\""one\"":\""two\""}}"")", typeof(object), context1);
|
||||
var result = await sut.EvaluateAsync("JSON.stringify(MyVariable)", typeof(object), context2);
|
||||
|
||||
Assert.Equal(@"{""foo"":""bar"",""child"":{""one"":""two""}}", result);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The EvaluateAsync method should be able to roundtrip a JSON object (which contains an embedded array) via JSON.parse and then JSON.stringify"), AutoMoqData]
|
||||
public async Task EvaluateAsyncShouldBeAbleToRoundtripAJsonStringWithAnEmbeddedArrayViaJsonStringify([Frozen] IMediator mediator,
|
||||
[Frozen] IOptions<ScriptOptions> options,
|
||||
JavaScriptService sut,
|
||||
[StubActivityExecutionContext] ActivityExecutionContext context1,
|
||||
[StubActivityExecutionContext] ActivityExecutionContext context2)
|
||||
{
|
||||
object returnedValue = null;
|
||||
Mock.Get(mediator)
|
||||
.Setup(x => x.Publish(It.Is<EvaluatingJavaScriptExpression>(e => e.ActivityExecutionContext == context2), It.IsAny<CancellationToken>()))
|
||||
.Callback((EvaluatingJavaScriptExpression expression, CancellationToken t) => {
|
||||
expression.Engine.SetValue("MyVariable", returnedValue);
|
||||
});
|
||||
|
||||
returnedValue = await sut.EvaluateAsync(@"JSON.parse(""{\""foo\"":\""bar\"", \""child\"":[\""one\"",\""two\""]}"")", typeof(object), context1);
|
||||
var result = await sut.EvaluateAsync("JSON.stringify(MyVariable)", typeof(object), context2);
|
||||
|
||||
Assert.Equal(@"{""foo"":""bar"",""child"":[""one"",""two""]}", result);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The EvaluateAsync method should be able to roundtrip a JSON array via JSON.parse and then JSON.stringify"), AutoMoqData]
|
||||
public async Task EvaluateAsyncShouldBeAbleToRoundtripAJsonStringOfAnArrayViaJsonStringify([Frozen] IMediator mediator,
|
||||
[Frozen] IOptions<ScriptOptions> options,
|
||||
JavaScriptService sut,
|
||||
[StubActivityExecutionContext] ActivityExecutionContext context1,
|
||||
[StubActivityExecutionContext] ActivityExecutionContext context2)
|
||||
{
|
||||
object returnedValue = null;
|
||||
Mock.Get(mediator)
|
||||
.Setup(x => x.Publish(It.Is<EvaluatingJavaScriptExpression>(e => e.ActivityExecutionContext == context2), It.IsAny<CancellationToken>()))
|
||||
.Callback((EvaluatingJavaScriptExpression expression, CancellationToken t) => {
|
||||
expression.Engine.SetValue("MyVariable", returnedValue);
|
||||
});
|
||||
|
||||
returnedValue = await sut.EvaluateAsync(@"JSON.parse(""[\""foo\"",\""bar\""]"")", typeof(object), context1);
|
||||
var result = await sut.EvaluateAsync("JSON.stringify(MyVariable)", typeof(object), context2);
|
||||
|
||||
Assert.Equal(@"[""foo"",""bar""]", result);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "The EvaluateAsync method should be able to access a property of an object which was created via JSON.parse"), AutoMoqData]
|
||||
public async Task EvaluateAsyncShouldBeAbleToAccessAPropertyWhichWasParsed([Frozen] IMediator mediator,
|
||||
[Frozen] IOptions<ScriptOptions> options,
|
||||
|
|
|
|||
Loading…
Reference in a new issue