Browse Source

Added support for anonymous objects. (#637)

Meikel Philipp 6 years ago
parent
commit
267809665b

+ 35 - 5
Jint.Tests/Runtime/EngineTests.cs

@@ -2641,8 +2641,8 @@ function output(x) {
 	var values = x.Values;
 	var generated = x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {});
 	return {
-        TestDictionary1 : values, 
-        TestDictionary2 : x.Values, 
+        TestDictionary1 : values,
+        TestDictionary2 : x.Values,
         TestDictionaryDirectAccess1 : Object.keys(x.Values).length,
         TestDictionaryDirectAccess2 : Object.keys(x.Values),
         TestDictionaryDirectAccess4 : Object.keys(x.Values).map(function(a){return x.Values[a];}),
@@ -2660,11 +2660,11 @@ function output(x) {
         TestGeneratedDictionarySum1 : Object.keys(generated).map(function(a){return{Key: a,Value:generated[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0),
         TestGeneratedDictionarySum2 : Object.keys(x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})).map(function(a){return{Key: a,Value:x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0),
         TestGeneratedDictionaryAverage1 : Object.keys(generated).map(function(a){return{Key: a,Value:generated[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0)/(Object.keys(generated).length||1),
-        TestGeneratedDictionaryAverage2 : Object.keys(x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})).map(function(a){return{Key: a,Value:x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0)/(Object.keys(x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})).length||1), 
+        TestGeneratedDictionaryAverage2 : Object.keys(x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})).map(function(a){return{Key: a,Value:x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})[a]};}).map(function(a){return a.Value;}).reduce(function(a, b) { return a + b; }, 0)/(Object.keys(x.Elements.reduce(function(_obj, _cur) {_obj[(function(a){return a.Name;})(_cur)] = (function(a){return a.Decimal;})(_cur);return _obj;}, {})).length||1),
         TestGeneratedDictionaryDirectAccess1 : Object.keys(generated),
         TestGeneratedDictionaryDirectAccess2 : Object.keys(generated).map(function(a){return generated[a];}),
-        TestGeneratedDictionaryDirectAccess3 : Object.keys(generated).length, 
-        TestList1 : elements.reduce(function(a, b) { return a + b; }, 0), 
+        TestGeneratedDictionaryDirectAccess3 : Object.keys(generated).length,
+        TestList1 : elements.reduce(function(a, b) { return a + b; }, 0),
         TestList2 : x.Elements.map(function(a){return a.Decimal;}).reduce(function(a, b) { return a + b; }, 0),
         TestList3 : x.Elements.map(function(a){return a.Decimal;}).reduce(function(a, b) { return a + b; }, 0),
         TestList4 : x.Elements.map(function(a){return a.Decimal;}).reduce(function(a, b) { return a + b; }, 0)/(x.Elements.length||1),
@@ -2767,5 +2767,35 @@ function output(x) {
             var voidCompletion = engine.Execute("try { JSON.parse('01') } catch (e) {}").GetCompletionValue();
             Assert.Equal(JsValue.Undefined, voidCompletion);
         }
+
+        [Fact]
+        public void ShouldParseAnonymousToTypeObject()
+        {
+            var obj = new Wrapper();
+            var engine = new Engine()
+                .SetValue("x", obj);
+            var js = @"
+x.test = {
+    name: 'Testificate',
+    init (a, b) {
+        return a + b
+    }
+}";
+            engine.Execute(js);
+
+            Assert.Equal("Testificate", obj.Test.Name);
+            Assert.Equal(5, obj.Test.Init(2, 3));
+        }
+
+        private class Wrapper
+        {
+            public Testificate Test { get; set; }
+        }
+
+        private class Testificate
+        {
+            public string Name { get; set; }
+            public Func<int, int, int> Init { get; set; }
+        }
     }
 }

+ 14 - 0
Jint/Extensions/JavascriptExtensions.cs

@@ -0,0 +1,14 @@
+using System.Text;
+
+namespace Jint.Extensions
+{
+    internal static class JavascriptExtensions
+    {
+        internal static string UpperToLowerCamelCase(this string str)
+        {
+            var arr = str.ToCharArray();
+            arr[0] = char.ToLowerInvariant(arr[0]);
+            return new string(arr);
+        }
+    }
+}

+ 43 - 0
Jint/Extensions/ReflectionExtensions.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Reflection;
+
+namespace Jint.Extensions
+{
+    internal static class ReflectionExtensions
+    {
+        internal static void SetValue(this MemberInfo memberInfo, object forObject, object value)
+        {
+            switch (memberInfo.MemberType)
+            {
+                case MemberTypes.Field:
+                    var fieldInfo = (FieldInfo) memberInfo;
+                    if (fieldInfo.FieldType == value?.GetType())
+                    {
+                        fieldInfo.SetValue(forObject, value);
+                    }
+
+                    break;
+                case MemberTypes.Property:
+                    var propertyInfo = (PropertyInfo) memberInfo;
+                    if (propertyInfo.PropertyType == value?.GetType())
+                    {
+                        propertyInfo.SetValue(forObject, value);
+                    }
+                    break;
+            }
+        }
+
+        internal static Type GetDefinedType(this MemberInfo memberInfo)
+        {
+            switch (memberInfo)
+            {
+                case PropertyInfo propertyInfo:
+                    return propertyInfo.PropertyType;
+                case FieldInfo fieldInfo:
+                    return fieldInfo.FieldType;
+            }
+
+            return null;
+        }
+    }
+}

+ 57 - 0
Jint/Runtime/Interop/DefaultTypeConverter.cs

@@ -1,8 +1,11 @@
 using System;
 using System.Collections.Concurrent;
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Dynamic;
 using System.Linq.Expressions;
 using System.Reflection;
+using Jint.Extensions;
 using Jint.Native;
 
 namespace Jint.Runtime.Interop
@@ -200,6 +203,60 @@ namespace Jint.Runtime.Interop
                 type = Nullable.GetUnderlyingType(type);
             }
 
+            if (value is ExpandoObject eObj)
+            {
+                // public empty constructor required
+                var constructors = type.GetConstructors();
+                // value types
+                if (type.IsValueType && constructors.Length > 0)
+                {
+                    return null;
+                }
+
+                // reference types - return null if no valid constructor is found
+                if(!type.IsValueType)
+                {
+                    var found = false;
+                    foreach (var constructor in constructors)
+                    {
+                        if (constructor.GetParameters().Length == 0 && constructor.IsPublic)
+                        {
+                            found = true;
+                            break;
+                        }
+                    }
+
+                    if (!found)
+                    {
+                        // found no valid constructor
+                        return null;
+                    }
+                }
+
+                var dict = (IDictionary<string, object>) eObj;
+                var obj = Activator.CreateInstance(type, ArrayExt.Empty<object>());
+
+                var members = type.GetMembers();
+                foreach (var member in members)
+                {
+                    // only use fields an properties
+                    if (member.MemberType != MemberTypes.Property &&
+                        member.MemberType != MemberTypes.Field)
+                    {
+                        continue;
+                    }
+
+                    var name = member.Name.UpperToLowerCamelCase();
+                    if (dict.TryGetValue(name, out var val))
+                    {
+                        var output = Convert(val, member.GetDefinedType(), formatProvider);
+                        member.SetValue(obj, output);
+                    }
+                }
+
+                return obj;
+            }
+
             return System.Convert.ChangeType(value, type, formatProvider);
         }