浏览代码

Direct C# interop replacing default serializing in JsonSerializer (#1232)

Aaron B 3 年之前
父节点
当前提交
bb77d1e1e1
共有 3 个文件被更改,包括 61 次插入3 次删除
  1. 43 0
      Jint.Tests/Runtime/InteropTests.Json.cs
  2. 12 3
      Jint/Native/Json/JsonSerializer.cs
  3. 6 0
      Jint/Options.cs

+ 43 - 0
Jint.Tests/Runtime/InteropTests.Json.cs

@@ -102,4 +102,47 @@ public partial class InteropTests
 
         Assert.Equal(expected, value);
     }
+    
+    [Fact]
+    public void CanStringifyUsingSerializeToJson()
+    {
+        object testObject = new { Foo = "bar", FooBar = new { Foo = 123.45, Foobar = new DateTime(2022, 7, 16, 0, 0, 0, DateTimeKind.Utc) } };
+        
+        // without interop
+        
+        var engineNoInterop = new Engine();
+        engineNoInterop.SetValue("TimeSpan", TypeReference.CreateTypeReference<TimeSpan>(engineNoInterop));
+        Assert.Throws<Jint.Runtime.JavaScriptException>(
+            () => engineNoInterop.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3))"));
+        
+        engineNoInterop.SetValue("TestObject", testObject);
+        Assert.Equal(
+            "{\"Foo\":\"bar\",\"FooBar\":{\"Foo\":123.45,\"Foobar\":\"2022-07-16T00:00:00.000Z\"}}",
+            engineNoInterop.Evaluate("JSON.stringify(TestObject)"));
+        
+        // interop using Newtonsoft serializer, for example with snake case naming
+        
+        string Serialize(object o) =>
+            Newtonsoft.Json.JsonConvert.SerializeObject(o,
+                new Newtonsoft.Json.JsonSerializerSettings {
+                    ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver {
+                        NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy() } });
+        var engine = new Engine(options =>
+        {
+            options.Interop.SerializeToJson = Serialize;
+        });
+        engine.SetValue("TimeSpan", TypeReference.CreateTypeReference<TimeSpan>(engine));
+        engine.SetValue("TestObject", testObject);
+
+        var expected = Serialize(TimeSpan.FromSeconds(3));
+        var actual = engine.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3));");
+        Assert.Equal(expected, actual);
+        
+        expected = Serialize(testObject);
+        actual = engine.Evaluate("JSON.stringify(TestObject)");
+        Assert.Equal(expected, actual);
+
+        actual = engine.Evaluate("JSON.stringify({ nestedValue: TestObject })");
+        Assert.Equal($@"{{""nestedValue"":{expected}}}", actual);
+    }
 }

+ 12 - 3
Jint/Native/Json/JsonSerializer.cs

@@ -201,9 +201,18 @@ namespace Jint.Native.Json
 
             if (value is ObjectInstance { IsCallable: false } objectInstance)
             {
-                return SerializesAsArray(objectInstance)
-                    ? SerializeJSONArray(objectInstance)
-                    : SerializeJSONObject(objectInstance);
+                if (SerializesAsArray(objectInstance))
+                {
+                    return SerializeJSONArray(objectInstance);
+                }
+
+                if (objectInstance is IObjectWrapper wrapper
+                    && _engine.Options.Interop.SerializeToJson is { } serialize)
+                {
+                    return serialize(wrapper.Target);
+                }
+
+                return SerializeJSONObject(objectInstance);
             }
 
             return JsValue.Undefined;

+ 6 - 0
Jint/Options.cs

@@ -307,6 +307,12 @@ namespace Jint
         /// Strategy to create a CLR object to hold converted <see cref="ObjectInstance"/>.
         /// </summary>
         public Func<ObjectInstance, IDictionary<string, object>> CreateClrObject = _ => new ExpandoObject();
+        
+        /// <summary>
+        /// When not null, is used to serialize any CLR object in an
+        /// <see cref="IObjectWrapper"/> passing through 'JSON.stringify'.
+        /// </summary>
+        public Func<object, string>? SerializeToJson { get; set; }
     }
 
     /// <summary>