Skip to content

Commit e7a204a

Browse files
committed
Fixing Node value serialization issue (#520)
1 parent 546f79b commit e7a204a

File tree

5 files changed

+119
-12
lines changed

5 files changed

+119
-12
lines changed

src/WebJobs.Script/Description/Node/NodeFunctionInvoker.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class NodeFunctionInvoker : FunctionInvokerBase
3030
private readonly Collection<FunctionBinding> _outputBindings;
3131
private readonly string _script;
3232
private readonly DictionaryJsonConverter _dictionaryJsonConverter = new DictionaryJsonConverter();
33+
private static readonly ExpandoObjectJsonConverter _expandoObjectJsonConverter = new ExpandoObjectJsonConverter();
3334
private readonly BindingMetadata _trigger;
3435
private readonly IMetricsLogger _metrics;
3536
private readonly string _entryPoint;
@@ -186,7 +187,7 @@ private async Task ProcessInputBindingsAsync(Binder binder, Dictionary<string, o
186187
executionContext["_inputs"] = inputs;
187188
}
188189

189-
private static async Task ProcessOutputBindingsAsync(Collection<FunctionBinding> outputBindings, object input, Binder binder,
190+
private static async Task ProcessOutputBindingsAsync(Collection<FunctionBinding> outputBindings, object input, Binder binder,
190191
Dictionary<string, object> bindingData, Dictionary<string, object> scriptExecutionContext, object functionResult)
191192
{
192193
if (outputBindings == null)
@@ -212,11 +213,7 @@ private static async Task ProcessOutputBindingsAsync(Collection<FunctionBinding>
212213
object value = null;
213214
if (bindings.TryGetValue(binding.Metadata.Name, out value) && value != null)
214215
{
215-
if (value.GetType() == typeof(ExpandoObject) ||
216-
(value is Array && value.GetType() != typeof(byte[])))
217-
{
218-
value = JsonConvert.SerializeObject(value);
219-
}
216+
value = ConvertBindingValue(value);
220217

221218
BindingContext bindingContext = new BindingContext
222219
{
@@ -230,6 +227,23 @@ private static async Task ProcessOutputBindingsAsync(Collection<FunctionBinding>
230227
}
231228
}
232229

230+
/// <summary>
231+
/// Perform any necessary conversions on the binding value received
232+
/// from the script.
233+
/// </summary>
234+
internal static object ConvertBindingValue(object value)
235+
{
236+
if (value.GetType() == typeof(ExpandoObject) ||
237+
(value is Array && value.GetType() != typeof(byte[])))
238+
{
239+
// objects and arrays we serialize to string before
240+
// passing to the binding layer
241+
value = JsonConvert.SerializeObject(value, _expandoObjectJsonConverter);
242+
}
243+
244+
return value;
245+
}
246+
233247
protected override void OnScriptFileChanged(object sender, FileSystemEventArgs e)
234248
{
235249
if (_scriptFunc == null)
@@ -274,7 +288,7 @@ private Dictionary<string, object> CreateScriptExecutionContext(object input, Da
274288
// TraceWriter. Might happen if a function tries to
275289
// log after calling done()
276290
}
277-
}
291+
}
278292

279293
return Task.FromResult<object>(null);
280294
});
@@ -393,12 +407,12 @@ private static Dictionary<string, object> NormalizeBindingData(Dictionary<string
393407
return normalizedBindingData;
394408
}
395409

396-
private static bool IsEdgeSupportedType(Type type)
410+
internal static bool IsEdgeSupportedType(Type type)
397411
{
398-
if (type == typeof(int) ||
399-
type == typeof(double) ||
400-
type == typeof(string) ||
401-
type == typeof(bool) ||
412+
if (type == typeof(int) ||
413+
type == typeof(double) ||
414+
type == typeof(string) ||
415+
type == typeof(bool) ||
402416
type == typeof(byte[]) ||
403417
type == typeof(object[]))
404418
{
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Dynamic;
7+
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Linq;
9+
10+
namespace Microsoft.Azure.WebJobs.Script
11+
{
12+
internal class ExpandoObjectJsonConverter : JsonConverter
13+
{
14+
public override bool CanConvert(Type objectType)
15+
{
16+
return objectType == typeof(ExpandoObject);
17+
}
18+
19+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
20+
{
21+
WriteExpandoObject(writer, (ExpandoObject)value, serializer);
22+
}
23+
24+
private static void WriteExpandoObject(JsonWriter writer, ExpandoObject value, JsonSerializer serializer)
25+
{
26+
writer.WriteStartObject();
27+
28+
var values = value as IDictionary<string, object>;
29+
foreach (var pair in values)
30+
{
31+
if (pair.Value != null && pair.Value is Delegate)
32+
{
33+
// skip types like Functions, etc.
34+
continue;
35+
}
36+
37+
writer.WritePropertyName(pair.Key);
38+
39+
if (pair.Value is ExpandoObject)
40+
{
41+
WriteExpandoObject(writer, (ExpandoObject)pair.Value, serializer);
42+
}
43+
else
44+
{
45+
serializer.Serialize(writer, pair.Value);
46+
}
47+
}
48+
49+
writer.WriteEndObject();
50+
}
51+
52+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
53+
{
54+
throw new NotImplementedException();
55+
}
56+
}
57+
}

src/WebJobs.Script/WebJobs.Script.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@
359359
<Compile Include="Diagnostics\MetricEvent.cs" />
360360
<Compile Include="Diagnostics\MetricsLogger.cs" />
361361
<Compile Include="Diagnostics\HostStartedEvent.cs" />
362+
<Compile Include="ExpandoObjectJsonConverter.cs" />
362363
<Compile Include="Extensions\TraceWriterExtensions.cs" />
363364
<Compile Include="GlobalSuppressions.cs" />
364365
<Compile Include="Extensions\INameResolverExtensions.cs" />
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Dynamic;
7+
using System.Threading.Tasks;
8+
using Microsoft.Azure.WebJobs.Script.Description;
9+
using Xunit;
10+
11+
namespace Microsoft.Azure.WebJobs.Script.Tests
12+
{
13+
public class NodeFunctionInvokerTests
14+
{
15+
[Fact]
16+
public static void ConvertBindingValue_PerformsExpectedConversions()
17+
{
18+
var c = new ExpandoObject() as IDictionary<string, object>;
19+
c["A"] = "Testing";
20+
c["B"] = 1234;
21+
c["C"] = new object[] { 1, "Two", 3 };
22+
23+
// don't expect functions to be serialized
24+
c["D"] = (Func<object, Task<object>>)(p => { return Task.FromResult<object>(null); });
25+
26+
var o = new ExpandoObject() as IDictionary<string, object>;
27+
o["A"] = "Testing";
28+
o["B"] = c;
29+
30+
string json = (string)NodeFunctionInvoker.ConvertBindingValue(o);
31+
Assert.Equal("{\"A\":\"Testing\",\"B\":{\"A\":\"Testing\",\"B\":1234,\"C\":[1,\"Two\",3]}}", json.Replace(" ", string.Empty));
32+
}
33+
}
34+
}

test/WebJobs.Script.Tests/WebJobs.Script.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@
336336
<Compile Include="FunctionGeneratorTests.cs" />
337337
<Compile Include="Description\DotNet\CSharp\PackageAssemblyResolverTests.cs" />
338338
<Compile Include="MetricsEventManagerTests.cs" />
339+
<Compile Include="NodeFunctionInvokerTests.cs" />
339340
<Compile Include="PowerShellInvokerTests.cs" />
340341
<Compile Include="Properties\Resources.Designer.cs">
341342
<AutoGen>True</AutoGen>

0 commit comments

Comments
 (0)