Explorar o código

Make JsValue implement IConvertible (#1713)

* allow disabling ExpandoObject conversion
Marko Lahma hai 1 ano
pai
achega
16365067f0

+ 18 - 0
Jint.Tests/Runtime/ExtensionMethods/ExtensionMethodsTest.cs

@@ -305,5 +305,23 @@ namespace Jint.Tests.Runtime.ExtensionMethods
             Assert.Equal(false, baseObservableResult.Last);
         }
 
+        [Fact]
+        public void CanProjectAndGroupWhenExpandoObjectWrappingDisabled()
+        {
+            var engine = new Engine(options =>
+            {
+                options.AllowClr().AddExtensionMethods(typeof(Enumerable));
+                // prevent ExpandoObject wrapping
+                options.Interop.CreateClrObject = null;
+            });
+            engine.Execute("var a = [ 2, 4 ];");
+
+            var selected = engine.Evaluate("JSON.stringify(a.Select(m => ({a:m,b:m})).ToArray());").AsString();
+            Assert.Equal("""[{"a":2,"b":2},{"a":4,"b":4}]""", selected);
+
+            var grouped1 = engine.Evaluate("a.GroupBy(m => ({a:m,b:m})).ToArray()").AsArray();
+            var grouped = engine.Evaluate("JSON.stringify(a.GroupBy(m => ({a:m,b:m})).Select(x => x.Key).ToArray());").AsString();
+            Assert.Equal("""[{"a":2,"b":2},{"a":4,"b":4}]""", grouped);
+        }
     }
 }

+ 105 - 0
Jint/Native/JsValue.Convertible.cs

@@ -0,0 +1,105 @@
+using Jint.Runtime;
+
+namespace Jint.Native;
+
+public partial class JsValue : IConvertible
+{
+    TypeCode IConvertible.GetTypeCode()
+    {
+        var type = _type & ~InternalTypes.InternalFlags;
+        return type switch
+        {
+            InternalTypes.Boolean => TypeCode.Boolean,
+            InternalTypes.String => TypeCode.String,
+            InternalTypes.Number => TypeCode.Double,
+            InternalTypes.Integer => TypeCode.Int32,
+            InternalTypes.PrivateName => TypeCode.String,
+            InternalTypes.Undefined => TypeCode.Object,
+            InternalTypes.Null => TypeCode.Object,
+            InternalTypes.Object => TypeCode.Object,
+            InternalTypes.PlainObject => TypeCode.Object,
+            InternalTypes.Array => TypeCode.Object,
+            _ => TypeCode.Empty
+        };
+    }
+
+    bool IConvertible.ToBoolean(IFormatProvider? provider)
+    {
+        return this.AsBoolean();
+    }
+
+    byte IConvertible.ToByte(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    char IConvertible.ToChar(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    DateTime IConvertible.ToDateTime(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    decimal IConvertible.ToDecimal(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    double IConvertible.ToDouble(IFormatProvider? provider)
+    {
+        return this.AsNumber();
+    }
+
+    short IConvertible.ToInt16(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    int IConvertible.ToInt32(IFormatProvider? provider)
+    {
+        return this.AsInteger();
+    }
+
+    long IConvertible.ToInt64(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    sbyte IConvertible.ToSByte(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    float IConvertible.ToSingle(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    string IConvertible.ToString(IFormatProvider? provider)
+    {
+        return this.AsString();
+    }
+
+    object IConvertible.ToType(Type conversionType, IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    ushort IConvertible.ToUInt16(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    uint IConvertible.ToUInt32(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+
+    ulong IConvertible.ToUInt64(IFormatProvider? provider)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 1 - 1
Jint/Native/JsValue.cs

@@ -13,7 +13,7 @@ using Jint.Runtime.Interop;
 
 namespace Jint.Native
 {
-    public abstract class JsValue : IEquatable<JsValue>
+    public abstract partial class JsValue : IEquatable<JsValue>
     {
         public static readonly JsValue Undefined = new JsUndefined();
         public static readonly JsValue Null = new JsNull();

+ 7 - 1
Jint/Native/Object/ObjectInstance.cs

@@ -1087,7 +1087,13 @@ namespace Jint.Native.Object
                         break;
                     }
 
-                    var o = _engine.Options.Interop.CreateClrObject(this);
+                    var func = _engine.Options.Interop.CreateClrObject;
+                    if (func is null)
+                    {
+                        goto default;
+                    }
+
+                    var o = func(this);
                     foreach (var p in GetOwnProperties())
                     {
                         if (!p.Value.Enumerable)

+ 1 - 1
Jint/Options.cs

@@ -326,7 +326,7 @@ namespace Jint
         /// <summary>
         /// Strategy to create a CLR object to hold converted <see cref="ObjectInstance"/>.
         /// </summary>
-        public Func<ObjectInstance, IDictionary<string, object?>> CreateClrObject = _ => new ExpandoObject();
+        public Func<ObjectInstance, IDictionary<string, object?>>? CreateClrObject = _ => new ExpandoObject();
 
         /// <summary>
         /// Strategy to create a CLR object from TypeReference.