Browse Source

Fix JSON.stringify for lists (#1006)

* split InteropTests a bit
* cleanup JsonSerializer a bit
Marko Lahma 3 years ago
parent
commit
b582c8b3e1

+ 185 - 0
Jint.Tests/Runtime/InteropTests.Dynamic.cs

@@ -0,0 +1,185 @@
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public partial class InteropTests
+    {
+        [Fact]
+        public void CanAccessExpandoObject()
+        {
+            var engine = new Engine();
+            dynamic expando = new ExpandoObject();
+            expando.Name = "test";
+            engine.SetValue("expando", expando);
+            Assert.Equal("test", engine.Evaluate("expando.Name").ToString());
+        }
+
+        [Fact]
+        public void CanAccessMemberNamedItemThroughExpando()
+        {
+            var parent = (IDictionary<string, object>) new ExpandoObject();
+            var child = (IDictionary<string, object>) new ExpandoObject();
+            var values = (IDictionary<string, object>) new ExpandoObject();
+
+            parent["child"] = child;
+            child["item"] = values;
+            values["title"] = "abc";
+
+            _engine.SetValue("parent", parent);
+            Assert.Equal("abc", _engine.Evaluate("parent.child.item.title"));
+        }
+
+
+        [Fact]
+        public void EngineShouldStringifyAnExpandoObjectCorrectly()
+        {
+            var engine = new Engine();
+
+            dynamic expando = new ExpandoObject();
+            expando.foo = 5;
+            expando.bar = "A string";
+            engine.SetValue(nameof(expando), expando);
+
+            var result = engine.Evaluate($"JSON.stringify({nameof(expando)})").AsString();
+            Assert.Equal("{\"foo\":5,\"bar\":\"A string\"}", result);
+        }
+
+        [Fact]
+        public void EngineShouldStringifyAnExpandoObjectWithValuesCorrectly()
+        {
+            // https://github.com/sebastienros/jint/issues/995
+            var engine = new Engine();
+
+            dynamic expando = new ExpandoObject();
+            expando.Values = 1;
+            engine.SetValue("expando", expando);
+
+            Assert.Equal("{\"Values\":1}", engine.Evaluate($"JSON.stringify(expando)").AsString());
+        }
+
+        [Fact]
+        public void ShouldForOfOnExpandoObject()
+        {
+            dynamic o = new ExpandoObject();
+            o.a = 1;
+            o.b = 2;
+
+            _engine.SetValue("dynamic", o);
+
+            var result = _engine.Evaluate("var l = ''; for (var x of dynamic) l += x; return l;").AsString();
+
+            Assert.Equal("a,1b,2", result);
+        }
+
+        [Fact]
+        public void ShouldConvertObjectInstanceToExpando()
+        {
+            _engine.Evaluate("var o = {a: 1, b: 'foo'}");
+            var result = _engine.GetValue("o");
+
+            dynamic value = result.ToObject();
+
+            Assert.Equal(1, value.a);
+            Assert.Equal("foo", value.b);
+
+            var dic = (IDictionary<string, object>) result.ToObject();
+
+            Assert.Equal(1d, dic["a"]);
+            Assert.Equal("foo", dic["b"]);
+        }
+
+        [Fact]
+        public void EngineShouldStringifyAnJObjectArrayWithValuesCorrectly()
+        {
+            //https://github.com/OrchardCMS/OrchardCore/issues/10648
+            var engine = new Engine();
+            var queryResults = new List<dynamic>();
+            queryResults.Add(new { Text = "Text1", Value = 1 });
+            queryResults.Add(new { Text = "Text2", Value = 2 });
+
+            engine.SetValue("testSubject", queryResults.ToArray());
+            var fromEngine2 = engine.Evaluate("return JSON.stringify(testSubject);");
+            var result2 = fromEngine2.ToString();
+            Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result2);
+        }
+
+        [Fact]
+        public void EngineShouldStringifyDynamicObjectListWithValuesCorrectly()
+        {
+            var engine = new Engine();
+            var source = new dynamic[] { new { Text = "Text1", Value = 1 }, new { Text = "Text2", Value = 2 } };
+
+            var objects = source.ToList();
+            engine.SetValue("testSubject", objects);
+            var fromEngine = engine.Evaluate("return JSON.stringify(testSubject);");
+            var result = fromEngine.ToString();
+            Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result);
+        }
+
+        [Fact]
+        public void EngineShouldStringifyDynamicObjectArrayWithValuesCorrectly()
+        {
+            var engine = new Engine();
+            var source = new dynamic[] { new { Text = "Text1", Value = 1 }, new { Text = "Text2", Value = 2 } };
+
+            engine.SetValue("testSubject", source.AsEnumerable());
+            var fromEngine = engine.Evaluate("return JSON.stringify(testSubject);");
+            var result = fromEngine.ToString();
+            Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result);
+        }
+
+        [Fact]
+        public void CanAccessDynamicObject()
+        {
+            var test = new DynamicClass();
+            var engine = new Engine();
+
+            engine.SetValue("test", test);
+
+            Assert.Equal("a", engine.Evaluate("test.a").AsString());
+            Assert.Equal("b", engine.Evaluate("test.b").AsString());
+
+            engine.Evaluate("test.a = 5; test.b = 10; test.Name = 'Jint'");
+
+            Assert.Equal(5, engine.Evaluate("test.a").AsNumber());
+            Assert.Equal(10, engine.Evaluate("test.b").AsNumber());
+
+            Assert.Equal("Jint", engine.Evaluate("test.Name").AsString());
+            Assert.True(engine.Evaluate("test.ContainsKey('a')").AsBoolean());
+            Assert.True(engine.Evaluate("test.ContainsKey('b')").AsBoolean());
+            Assert.False(engine.Evaluate("test.ContainsKey('c')").AsBoolean());
+        }
+
+        private class DynamicClass : DynamicObject
+        {
+            private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
+
+            public override bool TryGetMember(GetMemberBinder binder, out object result)
+            {
+                result = binder.Name;
+                if (_properties.TryGetValue(binder.Name, out var value))
+                {
+                    result = value;
+                }
+
+                return true;
+            }
+
+            public override bool TrySetMember(SetMemberBinder binder, object value)
+            {
+                _properties[binder.Name] = value;
+                return true;
+            }
+
+            public string Name { get; set; }
+
+            public bool ContainsKey(string key)
+            {
+                return _properties.ContainsKey(key);
+            }
+        }
+    }
+}

+ 94 - 0
Jint.Tests/Runtime/InteropTests.NewtonsoftJson.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Jint.Runtime;
+using Jint.Runtime.Interop;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public partial class InteropTests
+    {
+        [Fact]
+        public void AccessingJObjectShouldWork()
+        {
+            var o = new JObject
+            {
+                new JProperty("name", "test-name")
+            };
+            _engine.SetValue("o", o);
+            Assert.True(_engine.Evaluate("return o.name == 'test-name'").AsBoolean());
+        }
+
+        [Fact]
+        public void AccessingJArrayViaIntegerIndexShouldWork()
+        {
+            var o = new JArray("item1", "item2");
+            _engine.SetValue("o", o);
+            Assert.True(_engine.Evaluate("return o[0] == 'item1'").AsBoolean());
+            Assert.True(_engine.Evaluate("return o[1] == 'item2'").AsBoolean());
+        }
+
+        [Fact]
+        public void DictionaryLikeShouldCheckIndexerAndFallBackToProperty()
+        {
+            const string json = @"{ ""Type"": ""Cat"" }";
+            var jObjectWithTypeProperty = JObject.Parse(json);
+
+            _engine.SetValue("o", jObjectWithTypeProperty);
+
+            var typeResult = _engine.Evaluate("o.Type");
+
+            // JToken requires conversion
+            Assert.Equal("Cat", TypeConverter.ToString(typeResult));
+
+            // weak equality does conversions from native types
+            Assert.True(_engine.Evaluate("o.Type == 'Cat'").AsBoolean());
+        }
+
+        [Fact]
+        public void ShouldBeAbleToIndexJObjectWithStrings()
+        {
+            var engine = new Engine();
+
+            const string json = @"
+            {
+                'Properties': {
+                    'expirationDate': {
+                        'Value': '2021-10-09T00:00:00Z'
+                    }
+                }
+            }";
+
+            var obj = JObject.Parse(json);
+            engine.SetValue("o", obj);
+            var value = engine.Evaluate("o.Properties.expirationDate.Value");
+            var wrapper = Assert.IsAssignableFrom<ObjectWrapper>(value);
+            var token = wrapper.Target as JToken;
+            var localDateTimeString = DateTime.Parse("2021-10-09T00:00:00Z").ToUniversalTime();
+            Assert.Equal(localDateTimeString.ToString(), token.ToString());
+        }
+
+        // https://github.com/OrchardCMS/OrchardCore/issues/10648
+        [Fact]
+        public void EngineShouldStringifyAnJObjectListWithValuesCorrectly()
+        {
+            var engine = new Engine();
+            var queryResults = new List<dynamic>
+            {
+                new { Text = "Text1", Value = 1 },
+                new { Text = "Text2", Value = 2 }
+            };
+
+            engine.SetValue("testSubject", queryResults.Select(x => JObject.FromObject(x)));
+            var fromEngine = engine.Evaluate("return JSON.stringify(testSubject);");
+            var result = fromEngine.ToString();
+
+            // currently we do not materialize LINQ enumerables
+            // Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result);
+
+            Assert.Equal("{\"Current\":null}", result);
+        }
+    }
+}

+ 90 - 278
Jint.Tests/Runtime/InteropTests.cs

@@ -1,19 +1,16 @@
 using System;
 using System;
 using System.Collections;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Dynamic;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
 using Jint.Native;
 using Jint.Native;
-using Jint.Native.Array;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interop;
 using Jint.Tests.Runtime.Converters;
 using Jint.Tests.Runtime.Converters;
 using Jint.Tests.Runtime.Domain;
 using Jint.Tests.Runtime.Domain;
 using MongoDB.Bson;
 using MongoDB.Bson;
-using Newtonsoft.Json.Linq;
 using Shapes;
 using Shapes;
 using Xunit;
 using Xunit;
 
 
@@ -26,12 +23,12 @@ namespace Jint.Tests.Runtime
         public InteropTests()
         public InteropTests()
         {
         {
             _engine = new Engine(cfg => cfg.AllowClr(
             _engine = new Engine(cfg => cfg.AllowClr(
-                typeof(Shape).GetTypeInfo().Assembly,
-                typeof(Console).GetTypeInfo().Assembly,
-                typeof(System.IO.File).GetTypeInfo().Assembly))
-                .SetValue("log", new Action<object>(Console.WriteLine))
-                .SetValue("assert", new Action<bool>(Assert.True))
-                .SetValue("equal", new Action<object, object>(Assert.Equal))
+                        typeof(Shape).GetTypeInfo().Assembly,
+                        typeof(Console).GetTypeInfo().Assembly,
+                        typeof(System.IO.File).GetTypeInfo().Assembly))
+                    .SetValue("log", new Action<object>(Console.WriteLine))
+                    .SetValue("assert", new Action<bool>(Assert.True))
+                    .SetValue("equal", new Action<object, object>(Assert.Equal))
                 ;
                 ;
         }
         }
 
 
@@ -46,7 +43,10 @@ namespace Jint.Tests.Runtime
 
 
         public class Foo
         public class Foo
         {
         {
-            public static Bar GetBar() => new Bar();
+            public static Bar GetBar()
+            {
+                return new Bar();
+            }
         }
         }
 
 
         public class Bar
         public class Bar
@@ -62,32 +62,6 @@ namespace Jint.Tests.Runtime
             Assert.Equal("{\"Test\":\"123\"}", json);
             Assert.Equal("{\"Test\":\"123\"}", json);
         }
         }
 
 
-        [Fact]
-        public void EngineShouldStringifyAnExpandoObjectCorrectly()
-        {
-            var engine = new Engine();
-
-            dynamic expando = new ExpandoObject();
-            expando.foo = 5;
-            expando.bar = "A string";
-            engine.SetValue(nameof(expando), expando);
-
-            var result = engine.Evaluate($"JSON.stringify({nameof(expando)})").AsString();
-            Assert.Equal("{\"foo\":5,\"bar\":\"A string\"}", result);
-        }
-
-        [Fact]
-        public void EngineShouldStringifyAnExpandoObjectWithValuesCorrectly()
-        {
-            // https://github.com/sebastienros/jint/issues/995
-            var engine = new Engine();
-
-            dynamic expando = new ExpandoObject();
-            expando.Values = 1;
-            engine.SetValue("expando", expando);
-
-            Assert.Equal("{\"Values\":1}", engine.Evaluate($"JSON.stringify(expando)").AsString());
-        }
 
 
         [Fact]
         [Fact]
         public void EngineShouldStringifyADictionary()
         public void EngineShouldStringifyADictionary()
@@ -106,9 +80,10 @@ namespace Jint.Tests.Runtime
         {
         {
             var engine = new Engine();
             var engine = new Engine();
 
 
-            var dictionary = new Dictionary<string, object> {
+            var dictionary = new Dictionary<string, object>
+            {
                 { "foo", 5 },
                 { "foo", 5 },
-                { "bar", "A string"},
+                { "bar", "A string" }
             };
             };
             engine.SetValue(nameof(dictionary), dictionary);
             engine.SetValue(nameof(dictionary), dictionary);
 
 
@@ -173,9 +148,9 @@ namespace Jint.Tests.Runtime
 
 
             var argument = new Dictionary<string, object>
             var argument = new Dictionary<string, object>
             {
             {
-                {"item2", "item2 value"},
-                {"item", "item value"},
-                {"Item", "Item value"}
+                { "item2", "item2 value" },
+                { "item", "item value" },
+                { "Item", "Item value" }
             };
             };
 
 
             Assert.Equal("item2 value", _engine.Invoke("item2", argument));
             Assert.Equal("item2 value", _engine.Invoke("item2", argument));
@@ -290,6 +265,7 @@ namespace Jint.Tests.Runtime
         }
         }
 
 
         private delegate string callParams(params object[] values);
         private delegate string callParams(params object[] values);
+
         private delegate string callArgumentAndParams(string firstParam, params object[] values);
         private delegate string callArgumentAndParams(string firstParam, params object[] values);
 
 
         [Fact]
         [Fact]
@@ -463,15 +439,9 @@ namespace Jint.Tests.Runtime
 
 
         private class DoubleIndexedClass
         private class DoubleIndexedClass
         {
         {
-            public int this[int index]
-            {
-                get { return index; }
-            }
+            public int this[int index] => index;
 
 
-            public string this[string index]
-            {
-                get { return index; }
-            }
+            public string this[string index] => index;
         }
         }
 
 
         [Fact]
         [Fact]
@@ -508,7 +478,6 @@ namespace Jint.Tests.Runtime
         [Fact]
         [Fact]
         public void CanUseMultiGenericTypes()
         public void CanUseMultiGenericTypes()
         {
         {
-
             RunTest(@"
             RunTest(@"
                 var type = System.Collections.Generic.Dictionary(System.Int32, System.String);
                 var type = System.Collections.Generic.Dictionary(System.Int32, System.String);
                 var dictionary = new type();
                 var dictionary = new type();
@@ -577,27 +546,13 @@ namespace Jint.Tests.Runtime
         [Fact]
         [Fact]
         public void ShouldForOfOnDictionaries()
         public void ShouldForOfOnDictionaries()
         {
         {
-            _engine.SetValue("dict", new Dictionary<string, string> { {"a", "1"}, {"b", "2"} });
+            _engine.SetValue("dict", new Dictionary<string, string> { { "a", "1" }, { "b", "2" } });
 
 
             var result = _engine.Evaluate("var l = ''; for (var x of dict) l += x; return l;").AsString();
             var result = _engine.Evaluate("var l = ''; for (var x of dict) l += x; return l;").AsString();
 
 
             Assert.Equal("a,1b,2", result);
             Assert.Equal("a,1b,2", result);
         }
         }
 
 
-        [Fact]
-        public void ShouldForOfOnExpandoObject()
-        {
-            dynamic o = new ExpandoObject();
-            o.a = 1;
-            o.b = 2;
-
-            _engine.SetValue("dynamic", o);
-
-            var result = _engine.Evaluate("var l = ''; for (var x of dynamic) l += x; return l;").AsString();
-
-            Assert.Equal("a,1b,2", result);
-        }
-
         [Fact]
         [Fact]
         public void ShouldForOfOnEnumerable()
         public void ShouldForOfOnEnumerable()
         {
         {
@@ -624,7 +579,7 @@ namespace Jint.Tests.Runtime
         {
         {
             var p = new
             var p = new
             {
             {
-                Name = "Mickey Mouse",
+                Name = "Mickey Mouse"
             };
             };
 
 
             _engine.SetValue("p", p);
             _engine.SetValue("p", p);
@@ -658,7 +613,7 @@ namespace Jint.Tests.Runtime
             var o = new
             var o = new
             {
             {
                 x = new JsNumber(1),
                 x = new JsNumber(1),
-                y = new JsString("string"),
+                y = new JsString("string")
             };
             };
 
 
             _engine.SetValue("o", o);
             _engine.SetValue("o", o);
@@ -722,7 +677,7 @@ namespace Jint.Tests.Runtime
         {
         {
             var p = new Person
             var p = new Person
             {
             {
-                Name = "foo",
+                Name = "foo"
             };
             };
 
 
             _engine.SetValue("p", p);
             _engine.SetValue("p", p);
@@ -743,7 +698,7 @@ namespace Jint.Tests.Runtime
         {
         {
             var p = new Person
             var p = new Person
             {
             {
-                Name = "foo",
+                Name = "foo"
             };
             };
 
 
             _engine.SetValue("p", p);
             _engine.SetValue("p", p);
@@ -853,9 +808,20 @@ namespace Jint.Tests.Runtime
             public TestEnumInt32? NullableEnum { get; set; }
             public TestEnumInt32? NullableEnum { get; set; }
             public TestStruct? NullableStruct { get; set; }
             public TestStruct? NullableStruct { get; set; }
 
 
-            public void SetBool(bool value) => Bool = value;
-            public void SetInt(int value) => Int = value;
-            public void SetString(string value) => String = value;
+            public void SetBool(bool value)
+            {
+                Bool = value;
+            }
+
+            public void SetInt(int value)
+            {
+                Int = value;
+            }
+
+            public void SetString(string value)
+            {
+                String = value;
+            }
         }
         }
 
 
         [Fact]
         [Fact]
@@ -916,6 +882,7 @@ namespace Jint.Tests.Runtime
                     {
                     {
                         instance.SetPrototypeOf(engine.Realm.Intrinsics.Array.PrototypeObject);
                         instance.SetPrototypeOf(engine.Realm.Intrinsics.Array.PrototypeObject);
                     }
                     }
+
                     return instance;
                     return instance;
                 })
                 })
             );
             );
@@ -937,16 +904,6 @@ namespace Jint.Tests.Runtime
             Assert.Equal("John", name);
             Assert.Equal("John", name);
         }
         }
 
 
-        [Fact]
-        public void CanAccessExpandoObject()
-        {
-            var engine = new Engine();
-            dynamic expando = new ExpandoObject();
-            expando.Name = "test";
-            engine.SetValue("expando", expando);
-            Assert.Equal("test", engine.Evaluate("expando.Name").ToString());
-        }
-
         [Fact]
         [Fact]
         public void ShouldConvertArrayToArrayInstance()
         public void ShouldConvertArrayToArrayInstance()
         {
         {
@@ -995,7 +952,7 @@ namespace Jint.Tests.Runtime
         {
         {
             JsValue adder(JsValue argValue)
             JsValue adder(JsValue argValue)
             {
             {
-                ArrayInstance args = argValue.AsArray();
+                var args = argValue.AsArray();
                 double sum = 0;
                 double sum = 0;
                 foreach (var item in args)
                 foreach (var item in args)
                 {
                 {
@@ -1004,8 +961,10 @@ namespace Jint.Tests.Runtime
                         sum += item.AsNumber();
                         sum += item.AsNumber();
                     }
                     }
                 }
                 }
+
                 return sum;
                 return sum;
             }
             }
+
             var result = _engine.SetValue("getSum", new Func<JsValue, JsValue>(adder))
             var result = _engine.SetValue("getSum", new Func<JsValue, JsValue>(adder))
                 .Evaluate("getSum([1,2,3]);");
                 .Evaluate("getSum([1,2,3]);");
 
 
@@ -1024,10 +983,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         [Fact]
         public void ShouldAllowBooleanCoercion()
         public void ShouldAllowBooleanCoercion()
         {
         {
-            var engine = new Engine(options =>
-            {
-                options.Interop.ValueCoercion = ValueCoercionType.Boolean;
-            });
+            var engine = new Engine(options => { options.Interop.ValueCoercion = ValueCoercionType.Boolean; });
 
 
             engine.SetValue("o", new TestClass());
             engine.SetValue("o", new TestClass());
             Assert.True(engine.Evaluate("o.Bool = 1; return o.Bool;").AsBoolean());
             Assert.True(engine.Evaluate("o.Bool = 1; return o.Bool;").AsBoolean());
@@ -1057,10 +1013,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         [Fact]
         public void ShouldAllowNumberCoercion()
         public void ShouldAllowNumberCoercion()
         {
         {
-            var engine = new Engine(options =>
-            {
-                options.Interop.ValueCoercion = ValueCoercionType.Number;
-            });
+            var engine = new Engine(options => { options.Interop.ValueCoercion = ValueCoercionType.Number; });
 
 
             engine.SetValue("o", new TestClass());
             engine.SetValue("o", new TestClass());
             Assert.Equal(1, engine.Evaluate("o.Int = true; return o.Int;").AsNumber());
             Assert.Equal(1, engine.Evaluate("o.Int = true; return o.Int;").AsNumber());
@@ -1089,10 +1042,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         [Fact]
         public void ShouldAllowStringCoercion()
         public void ShouldAllowStringCoercion()
         {
         {
-            var engine = new Engine(options =>
-            {
-                options.Interop.ValueCoercion = ValueCoercionType.String;
-            });
+            var engine = new Engine(options => { options.Interop.ValueCoercion = ValueCoercionType.String; });
 
 
             // basic premise, booleans in JS are lower-case, so should the the toString under interop
             // basic premise, booleans in JS are lower-case, so should the the toString under interop
             Assert.Equal("true", engine.Evaluate("'' + true").AsString());
             Assert.Equal("true", engine.Evaluate("'' + true").AsString());
@@ -1161,24 +1111,6 @@ namespace Jint.Tests.Runtime
             Assert.Equal("foo", value);
             Assert.Equal("foo", value);
         }
         }
 
 
-        [Fact]
-        public void ShouldConvertObjectInstanceToExpando()
-        {
-            _engine.Evaluate("var o = {a: 1, b: 'foo'}");
-            var result = _engine.GetValue("o");
-
-            dynamic value = result.ToObject();
-
-            Assert.Equal(1, value.a);
-            Assert.Equal("foo", value.b);
-
-            var dic = (IDictionary<string, object>) result.ToObject();
-
-            Assert.Equal(1d, dic["a"]);
-            Assert.Equal("foo", dic["b"]);
-
-        }
-
         [Fact]
         [Fact]
         public void ShouldNotTryToConvertCompatibleTypes()
         public void ShouldNotTryToConvertCompatibleTypes()
         {
         {
@@ -1482,7 +1414,6 @@ namespace Jint.Tests.Runtime
         [Fact]
         [Fact]
         public void CanSetCustomConverters()
         public void CanSetCustomConverters()
         {
         {
-
             var engine1 = new Engine();
             var engine1 = new Engine();
             engine1.SetValue("p", new { Test = true });
             engine1.SetValue("p", new { Test = true });
             engine1.Execute("var result = p.Test;");
             engine1.Execute("var result = p.Test;");
@@ -1492,7 +1423,6 @@ namespace Jint.Tests.Runtime
             engine2.SetValue("p", new { Test = true });
             engine2.SetValue("p", new { Test = true });
             engine2.Execute("var result = p.Test;");
             engine2.Execute("var result = p.Test;");
             Assert.False((bool) engine2.GetValue("result").ToObject());
             Assert.False((bool) engine2.GetValue("result").ToObject());
-
         }
         }
 
 
         [Fact]
         [Fact]
@@ -1511,7 +1441,7 @@ namespace Jint.Tests.Runtime
         {
         {
             var p = new Person
             var p = new Person
             {
             {
-                Age = 1,
+                Age = 1
             };
             };
 
 
             _engine.SetValue("p", p);
             _engine.SetValue("p", p);
@@ -1575,7 +1505,7 @@ namespace Jint.Tests.Runtime
         {
         {
             var s = new Circle
             var s = new Circle
             {
             {
-                Color = Colors.Red,
+                Color = Colors.Red
             };
             };
 
 
             _engine.SetValue("s", s);
             _engine.SetValue("s", s);
@@ -1598,42 +1528,42 @@ namespace Jint.Tests.Runtime
             Assert.Equal(Colors.Blue | Colors.Green, s.Color);
             Assert.Equal(Colors.Blue | Colors.Green, s.Color);
         }
         }
 
 
-        enum TestEnumInt32 : int
+        private enum TestEnumInt32 : int
         {
         {
             None,
             None,
             One = 1,
             One = 1,
             Min = int.MaxValue,
             Min = int.MaxValue,
-            Max = int.MaxValue,
+            Max = int.MaxValue
         }
         }
 
 
-        enum TestEnumUInt32 : uint
+        private enum TestEnumUInt32 : uint
         {
         {
             None,
             None,
             One = 1,
             One = 1,
             Min = uint.MaxValue,
             Min = uint.MaxValue,
-            Max = uint.MaxValue,
+            Max = uint.MaxValue
         }
         }
 
 
-        enum TestEnumInt64 : long
+        private enum TestEnumInt64 : long
         {
         {
             None,
             None,
             One = 1,
             One = 1,
             Min = long.MaxValue,
             Min = long.MaxValue,
-            Max = long.MaxValue,
+            Max = long.MaxValue
         }
         }
 
 
-        enum TestEnumUInt64 : ulong
+        private enum TestEnumUInt64 : ulong
         {
         {
             None,
             None,
             One = 1,
             One = 1,
             Min = ulong.MaxValue,
             Min = ulong.MaxValue,
-            Max = ulong.MaxValue,
+            Max = ulong.MaxValue
         }
         }
 
 
-        void TestEnum<T>(T enumValue)
+        private void TestEnum<T>(T enumValue)
         {
         {
-            object i = Convert.ChangeType(enumValue, Enum.GetUnderlyingType(typeof(T)));
-            string s = Convert.ToString(i, CultureInfo.InvariantCulture);
+            var i = Convert.ChangeType(enumValue, Enum.GetUnderlyingType(typeof(T)));
+            var s = Convert.ToString(i, CultureInfo.InvariantCulture);
             var o = new Tuple<T>(enumValue);
             var o = new Tuple<T>(enumValue);
             _engine.SetValue("o", o);
             _engine.SetValue("o", o);
             RunTest("assert(o.Item1 === " + s + ");");
             RunTest("assert(o.Item1 === " + s + ");");
@@ -1699,7 +1629,7 @@ namespace Jint.Tests.Runtime
         {
         {
             var s = new Circle
             var s = new Circle
             {
             {
-                Color = Colors.Red,
+                Color = Colors.Red
             };
             };
 
 
             _engine.SetValue("s", s);
             _engine.SetValue("s", s);
@@ -2067,7 +1997,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         [Fact]
         public void ShouldCatchAllClrExceptions()
         public void ShouldCatchAllClrExceptions()
         {
         {
-            string exceptionMessage = "myExceptionMessage";
+            var exceptionMessage = "myExceptionMessage";
 
 
             var engine = new Engine(o => o.CatchClrExceptions())
             var engine = new Engine(o => o.CatchClrExceptions())
                 .SetValue("throwMyException", new Action(() => { throw new Exception(exceptionMessage); }))
                 .SetValue("throwMyException", new Action(() => { throw new Exception(exceptionMessage); }))
@@ -2098,24 +2028,26 @@ namespace Jint.Tests.Runtime
             Assert.Equal(engine.Invoke("throwException2").AsString(), exceptionMessage);
             Assert.Equal(engine.Invoke("throwException2").AsString(), exceptionMessage);
         }
         }
 
 
-        class MemberExceptionTest
+        private class MemberExceptionTest
         {
         {
             public MemberExceptionTest(bool throwOnCreate)
             public MemberExceptionTest(bool throwOnCreate)
             {
             {
                 if (throwOnCreate)
                 if (throwOnCreate)
+                {
                     throw new InvalidOperationException("thrown as requested");
                     throw new InvalidOperationException("thrown as requested");
+                }
             }
             }
 
 
             public JsValue ThrowingProperty1
             public JsValue ThrowingProperty1
             {
             {
-                get { throw new InvalidOperationException(); }
-                set { throw new InvalidOperationException(); }
+                get => throw new InvalidOperationException();
+                set => throw new InvalidOperationException();
             }
             }
 
 
             public object ThrowingProperty2
             public object ThrowingProperty2
             {
             {
-                get { throw new InvalidOperationException(); }
-                set { throw new InvalidOperationException(); }
+                get => throw new InvalidOperationException();
+                set => throw new InvalidOperationException();
             }
             }
 
 
             public void ThrowingFunction()
             public void ThrowingFunction()
@@ -2136,7 +2068,7 @@ namespace Jint.Tests.Runtime
             engine.SetValue("assert", new Action<bool>(Assert.True));
             engine.SetValue("assert", new Action<bool>(Assert.True));
             engine.SetValue("log", new Action<object>(Console.WriteLine));
             engine.SetValue("log", new Action<object>(Console.WriteLine));
             engine.SetValue("create", typeof(MemberExceptionTest));
             engine.SetValue("create", typeof(MemberExceptionTest));
-            engine.SetValue("instance", new MemberExceptionTest(throwOnCreate: false));
+            engine.SetValue("instance", new MemberExceptionTest(false));
 
 
             // Test calling a constructor that throws an exception
             // Test calling a constructor that throws an exception
             engine.Execute(@"
             engine.Execute(@"
@@ -2204,7 +2136,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         [Fact]
         public void ShouldCatchSomeExceptions()
         public void ShouldCatchSomeExceptions()
         {
         {
-            string exceptionMessage = "myExceptionMessage";
+            var exceptionMessage = "myExceptionMessage";
 
 
             var engine = new Engine(o => o.CatchClrExceptions(e => e is NotSupportedException))
             var engine = new Engine(o => o.CatchClrExceptions(e => e is NotSupportedException))
                 .SetValue("throwMyException1", new Action(() => { throw new NotSupportedException(exceptionMessage); }))
                 .SetValue("throwMyException1", new Action(() => { throw new NotSupportedException(exceptionMessage); }))
@@ -2263,8 +2195,8 @@ namespace Jint.Tests.Runtime
         {
         {
             var list = new List<Person>
             var list = new List<Person>
             {
             {
-                new Person {Name = "Mike"},
-                new Person {Name = "Mika"}
+                new Person { Name = "Mike" },
+                new Person { Name = "Mika" }
             };
             };
             _engine.SetValue("a", list);
             _engine.SetValue("a", list);
 
 
@@ -2288,8 +2220,8 @@ namespace Jint.Tests.Runtime
         {
         {
             var list = new[]
             var list = new[]
             {
             {
-                new Person {Name = "Mike"},
-                new Person {Name = "Mika"}
+                new Person { Name = "Mike" },
+                new Person { Name = "Mika" }
             };
             };
             _engine.SetValue("a", list);
             _engine.SetValue("a", list);
 
 
@@ -2313,8 +2245,8 @@ namespace Jint.Tests.Runtime
         {
         {
             var enumerable = new[]
             var enumerable = new[]
             {
             {
-                new Person {Name = "Mike"},
-                new Person {Name = "Mika"}
+                new Person { Name = "Mike" },
+                new Person { Name = "Mika" }
             }.Select(x => x);
             }.Select(x => x);
 
 
             _engine.SetValue("a", enumerable);
             _engine.SetValue("a", enumerable);
@@ -2359,12 +2291,12 @@ namespace Jint.Tests.Runtime
             Assert.Equal((uint) 0, c.As<ObjectInstance>().Length);
             Assert.Equal((uint) 0, c.As<ObjectInstance>().Length);
         }
         }
 
 
-        class DictionaryWrapper
+        private class DictionaryWrapper
         {
         {
             public IDictionary<string, object> Values { get; set; }
             public IDictionary<string, object> Values { get; set; }
         }
         }
 
 
-        class DictionaryTest
+        private class DictionaryTest
         {
         {
             public void Test1(IDictionary<string, object> values)
             public void Test1(IDictionary<string, object> values)
             {
             {
@@ -2399,7 +2331,7 @@ namespace Jint.Tests.Runtime
             var engine = new Engine();
             var engine = new Engine();
             var state = new Dictionary<string, object>
             var state = new Dictionary<string, object>
             {
             {
-                {"invoice", new Dictionary<string, object> {["number"] = "42"}}
+                { "invoice", new Dictionary<string, object> { ["number"] = "42" } }
             };
             };
             engine.SetValue("state", state);
             engine.SetValue("state", state);
 
 
@@ -2417,7 +2349,7 @@ namespace Jint.Tests.Runtime
             var engine = new Engine();
             var engine = new Engine();
             var state = new Dictionary<string, object>
             var state = new Dictionary<string, object>
             {
             {
-                {"invoice", new Dictionary<string, object> {["number"] = "42"}}
+                { "invoice", new Dictionary<string, object> { ["number"] = "42" } }
             };
             };
             engine.SetValue("state", state);
             engine.SetValue("state", state);
 
 
@@ -2459,8 +2391,8 @@ namespace Jint.Tests.Runtime
 
 
             engine.SetValue("netObj", new Dictionary<string, object>
             engine.SetValue("netObj", new Dictionary<string, object>
             {
             {
-                {"key1", "value1"},
-                {"key2", "value2"},
+                { "key1", "value1" },
+                { "key2", "value2" }
             });
             });
 
 
             var jsValue = engine.Evaluate("jsObj['key1']").AsString();
             var jsValue = engine.Evaluate("jsObj['key1']").AsString();
@@ -2502,43 +2434,6 @@ namespace Jint.Tests.Runtime
             Assert.Equal(678, engine.Evaluate("fia[0]").AsNumber());
             Assert.Equal(678, engine.Evaluate("fia[0]").AsNumber());
         }
         }
 
 
-        [Fact]
-        public void AccessingJObjectShouldWork()
-        {
-            var o = new JObject
-            {
-                new JProperty("name", "test-name")
-            };
-            _engine.SetValue("o", o);
-            Assert.True(_engine.Evaluate("return o.name == 'test-name'").AsBoolean());
-        }
-
-        [Fact]
-        public void AccessingJArrayViaIntegerIndexShouldWork()
-        {
-            var o = new JArray("item1", "item2");
-            _engine.SetValue("o", o);
-            Assert.True(_engine.Evaluate("return o[0] == 'item1'").AsBoolean());
-            Assert.True(_engine.Evaluate("return o[1] == 'item2'").AsBoolean());
-        }
-
-        [Fact]
-        public void DictionaryLikeShouldCheckIndexerAndFallBackToProperty()
-        {
-            const string json = @"{ ""Type"": ""Cat"" }";
-            var jObjectWithTypeProperty = JObject.Parse(json);
-
-            _engine.SetValue("o", jObjectWithTypeProperty);
-
-            var typeResult = _engine.Evaluate("o.Type");
-
-            // JToken requires conversion
-            Assert.Equal("Cat", TypeConverter.ToString(typeResult));
-
-            // weak equality does conversions from native types
-            Assert.True(_engine.Evaluate("o.Type == 'Cat'").AsBoolean());
-        }
-
         [Fact]
         [Fact]
         public void IndexingBsonProperties()
         public void IndexingBsonProperties()
         {
         {
@@ -2552,70 +2447,6 @@ namespace Jint.Tests.Runtime
             Assert.True(_engine.Evaluate("animals[0].Id == 1").AsBoolean());
             Assert.True(_engine.Evaluate("animals[0].Id == 1").AsBoolean());
         }
         }
 
 
-        [Fact]
-        public void CanAccessDynamicObject()
-        {
-            var test = new DynamicClass();
-            var engine = new Engine();
-
-            engine.SetValue("test", test);
-
-            Assert.Equal("a", engine.Evaluate("test.a").AsString());
-            Assert.Equal("b", engine.Evaluate("test.b").AsString());
-
-            engine.Evaluate("test.a = 5; test.b = 10; test.Name = 'Jint'");
-
-            Assert.Equal(5, engine.Evaluate("test.a").AsNumber());
-            Assert.Equal(10, engine.Evaluate("test.b").AsNumber());
-
-            Assert.Equal("Jint", engine.Evaluate("test.Name").AsString());
-            Assert.True(engine.Evaluate("test.ContainsKey('a')").AsBoolean());
-            Assert.True(engine.Evaluate("test.ContainsKey('b')").AsBoolean());
-            Assert.False(engine.Evaluate("test.ContainsKey('c')").AsBoolean());
-        }
-
-        [Fact]
-        public void CanAccessMemberNamedItemThroughExpando()
-        {
-            var parent = (IDictionary<string, object>) new ExpandoObject();
-            var child = (IDictionary<string, object>) new ExpandoObject();
-            var values = (IDictionary<string, object>) new ExpandoObject();
-
-            parent["child"] = child;
-            child["item"] = values;
-            values["title"] = "abc";
-
-            _engine.SetValue("parent", parent);
-            Assert.Equal("abc", _engine.Evaluate("parent.child.item.title"));
-        }
-
-        private class DynamicClass : DynamicObject
-        {
-            private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
-
-            public override bool TryGetMember(GetMemberBinder binder, out object result)
-            {
-                result = binder.Name;
-                if (_properties.TryGetValue(binder.Name, out var value))
-                {
-                    result = value;
-                }
-                return true;
-            }
-
-            public override bool TrySetMember(SetMemberBinder binder, object value)
-            {
-                _properties[binder.Name] = value;
-                return true;
-            }
-
-            public string Name { get; set; }
-            public bool ContainsKey(string key)
-            {
-                return _properties.ContainsKey(key);
-            }
-        }
-
         [Fact]
         [Fact]
         public void IntegerEnumResolutionShouldWork()
         public void IntegerEnumResolutionShouldWork()
         {
         {
@@ -2734,7 +2565,10 @@ namespace Jint.Tests.Runtime
         {
         {
             var engine = new Engine();
             var engine = new Engine();
 
 
-            static void Test(string message, object value) { Console.WriteLine(message); }
+            static void Test(string message, object value)
+            {
+                Console.WriteLine(message);
+            }
 
 
             engine.Realm.GlobalObject.FastAddProperty("global", engine.Realm.GlobalObject, true, true, true);
             engine.Realm.GlobalObject.FastAddProperty("global", engine.Realm.GlobalObject, true, true, true);
             engine.Realm.GlobalObject.FastAddProperty("test", new DelegateWrapper(engine, (Action<string, object>) Test), true, true, true);
             engine.Realm.GlobalObject.FastAddProperty("test", new DelegateWrapper(engine, (Action<string, object>) Test), true, true, true);
@@ -2797,9 +2631,10 @@ namespace Jint.Tests.Runtime
         {
         {
             var engine = new Engine();
             var engine = new Engine();
 
 
-            var dictionary = new Dictionary<string, object> {
+            var dictionary = new Dictionary<string, object>
+            {
                 { "foo", 5 },
                 { "foo", 5 },
-                { "bar", "A string"},
+                { "bar", "A string" }
             };
             };
             engine.SetValue("dictionary", dictionary);
             engine.SetValue("dictionary", dictionary);
 
 
@@ -2850,7 +2685,7 @@ namespace Jint.Tests.Runtime
         {
         {
             static IEnumerable<string> MemberNameCreator(MemberInfo prop)
             static IEnumerable<string> MemberNameCreator(MemberInfo prop)
             {
             {
-                var attributes = prop.GetCustomAttributes(typeof(CustomNameAttribute), inherit: true);
+                var attributes = prop.GetCustomAttributes(typeof(CustomNameAttribute), true);
                 if (attributes.Length > 0)
                 if (attributes.Length > 0)
                 {
                 {
                     foreach (CustomNameAttribute attribute in attributes)
                     foreach (CustomNameAttribute attribute in attributes)
@@ -2898,28 +2733,5 @@ namespace Jint.Tests.Runtime
             var ex = Assert.Throws<JavaScriptException>(() => engine.Execute("a.age = \"It won't work, but it's normal\""));
             var ex = Assert.Throws<JavaScriptException>(() => engine.Execute("a.age = \"It won't work, but it's normal\""));
             Assert.Equal("Input string was not in a correct format.", ex.Message);
             Assert.Equal("Input string was not in a correct format.", ex.Message);
         }
         }
-
-        [Fact]
-        public void ShouldBeAbleToIndexJObjectWithStrings()
-        {
-            var engine = new Engine();
-
-            const string json = @"
-            {
-                'Properties': {
-                    'expirationDate': {
-                        'Value': '2021-10-09T00:00:00Z'
-                    }
-                }
-            }";
-
-            var obj = JObject.Parse(json);
-            engine.SetValue("o", obj);
-            var value = engine.Evaluate("o.Properties.expirationDate.Value");
-            var wrapper = Assert.IsAssignableFrom<ObjectWrapper>(value);
-            var token = wrapper.Target as JToken;
-            var localDateTimeString = DateTime.Parse("2021-10-09T00:00:00Z").ToUniversalTime();
-            Assert.Equal(localDateTimeString.ToString(), token.ToString());
-        }
     }
     }
-}
+}

+ 1 - 1
Jint/Native/JsString.cs

@@ -13,7 +13,7 @@ namespace Jint.Native
         private static readonly JsString[] _intToStringJsValue;
         private static readonly JsString[] _intToStringJsValue;
 
 
         public static readonly JsString Empty = new JsString("");
         public static readonly JsString Empty = new JsString("");
-        private static readonly JsString NullString = new JsString("null");
+        internal static readonly JsString NullString = new JsString("null");
         internal static readonly JsString UndefinedString = new JsString("undefined");
         internal static readonly JsString UndefinedString = new JsString("undefined");
         internal static readonly JsString ObjectString = new JsString("object");
         internal static readonly JsString ObjectString = new JsString("object");
         internal static readonly JsString FunctionString = new JsString("function");
         internal static readonly JsString FunctionString = new JsString("function");

+ 35 - 26
Jint/Native/Json/JsonSerializer.cs

@@ -3,25 +3,26 @@ using System.Linq;
 using Jint.Collections;
 using Jint.Collections;
 using Jint.Native.Global;
 using Jint.Native.Global;
 using Jint.Native.Object;
 using Jint.Native.Object;
+using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
 
 
 namespace Jint.Native.Json
 namespace Jint.Native.Json
 {
 {
     public class JsonSerializer
     public class JsonSerializer
     {
     {
         private readonly Engine _engine;
         private readonly Engine _engine;
+        private ObjectTraverseStack _stack;
+        private string _indent, _gap;
+        private List<JsValue> _propertyList;
+        private JsValue _replacerFunction = Undefined.Instance;
 
 
         public JsonSerializer(Engine engine)
         public JsonSerializer(Engine engine)
         {
         {
             _engine = engine;
             _engine = engine;
         }
         }
 
 
-        private ObjectTraverseStack _stack;
-        private string _indent, _gap;
-        private List<JsValue> _propertyList;
-        private JsValue _replacerFunction = Undefined.Instance;
-
         public JsValue Serialize(JsValue value, JsValue replacer, JsValue space)
         public JsValue Serialize(JsValue value, JsValue replacer, JsValue space)
         {
         {
             _stack = new ObjectTraverseStack(_engine);
             _stack = new ObjectTraverseStack(_engine);
@@ -154,23 +155,22 @@ namespace Jint.Native.Json
                     case ObjectClass.Boolean:
                     case ObjectClass.Boolean:
                         value = TypeConverter.ToPrimitive(value);
                         value = TypeConverter.ToPrimitive(value);
                         break;
                         break;
-                    case ObjectClass.Array:
-                        value = SerializeArray(value);
-                        return value;
-                    case ObjectClass.Object:
-                        value = SerializeObject(value.AsObject());
+                    default:
+                        value = SerializesAsArray(value)
+                            ? SerializeArray(value)
+                            : SerializeObject(value.AsObject());
                         return value;
                         return value;
                 }
                 }
             }
             }
 
 
             if (ReferenceEquals(value, Null.Instance))
             if (ReferenceEquals(value, Null.Instance))
             {
             {
-                return "null";
+                return JsString.NullString;
             }
             }
 
 
             if (value.IsBoolean())
             if (value.IsBoolean())
             {
             {
-                return ((JsBoolean) value)._value ? "true" : "false";
+                return ((JsBoolean) value)._value ? JsString.TrueString : JsString.FalseString;
             }
             }
 
 
             if (value.IsString())
             if (value.IsString())
@@ -186,14 +186,14 @@ namespace Jint.Native.Json
                     return TypeConverter.ToJsString(value);
                     return TypeConverter.ToJsString(value);
                 }
                 }
 
 
-                return "null";
+                return JsString.NullString;
             }
             }
 
 
             var isCallable = value.IsObject() && value.AsObject() is ICallable;
             var isCallable = value.IsObject() && value.AsObject() is ICallable;
 
 
             if (value.IsObject() && isCallable == false)
             if (value.IsObject() && isCallable == false)
             {
             {
-                return value.AsObject().Class == ObjectClass.Array
+                return SerializesAsArray(value)
                     ? SerializeArray(value)
                     ? SerializeArray(value)
                     : SerializeObject(value.AsObject());
                     : SerializeObject(value.AsObject());
             }
             }
@@ -201,11 +201,18 @@ namespace Jint.Native.Json
             return JsValue.Undefined;
             return JsValue.Undefined;
         }
         }
 
 
+        private static bool SerializesAsArray(JsValue value)
+        {
+            return value.AsObject().Class == ObjectClass.Array || value is ObjectWrapper { IsArrayLike: true };
+        }
+
         private static string Quote(string value)
         private static string Quote(string value)
         {
         {
-            var sb = new System.Text.StringBuilder("\"");
+            using var stringBuilder = StringBuilderPool.Rent();
+            var sb = stringBuilder.Builder;
+            sb.Append("\"");
 
 
-            foreach (char c in value)
+            foreach (var c in value)
             {
             {
                 switch (c)
                 switch (c)
                 {
                 {
@@ -255,11 +262,14 @@ namespace Jint.Native.Json
             var len = TypeConverter.ToUint32(value.Get(CommonProperties.Length, value));
             var len = TypeConverter.ToUint32(value.Get(CommonProperties.Length, value));
             for (int i = 0; i < len; i++)
             for (int i = 0; i < len; i++)
             {
             {
-                var strP = Str(TypeConverter.ToString(i), value);
+                var strP = Str(i, value);
                 if (strP.IsUndefined())
                 if (strP.IsUndefined())
-                    strP = "null";
+                {
+                    strP = JsString.NullString;
+                }
                 partial.Add(strP.ToString());
                 partial.Add(strP.ToString());
             }
             }
+
             if (partial.Count == 0)
             if (partial.Count == 0)
             {
             {
                 _stack.Exit();
                 _stack.Exit();
@@ -269,14 +279,14 @@ namespace Jint.Native.Json
             string final;
             string final;
             if (_gap == "")
             if (_gap == "")
             {
             {
-                var separator = ",";
-                var properties = string.Join(separator, partial.ToArray());
+                const string separator = ",";
+                var properties = string.Join(separator, partial);
                 final = "[" + properties + "]";
                 final = "[" + properties + "]";
             }
             }
             else
             else
             {
             {
                 var separator = ",\n" + _indent;
                 var separator = ",\n" + _indent;
-                var properties = string.Join(separator, partial.ToArray());
+                var properties = string.Join(separator, partial);
                 final = "[\n" + _indent + properties + "\n" + stepback + "]";
                 final = "[\n" + _indent + properties + "\n" + stepback + "]";
             }
             }
 
 
@@ -295,8 +305,7 @@ namespace Jint.Native.Json
 
 
             var k = _propertyList ?? value.GetOwnProperties()
             var k = _propertyList ?? value.GetOwnProperties()
                 .Where(x => x.Value.Enumerable)
                 .Where(x => x.Value.Enumerable)
-                .Select(x => x.Key)
-                .ToList();
+                .Select(x => x.Key);
 
 
             var partial = new List<string>();
             var partial = new List<string>();
             foreach (var p in k)
             foreach (var p in k)
@@ -321,14 +330,14 @@ namespace Jint.Native.Json
             {
             {
                 if (_gap == "")
                 if (_gap == "")
                 {
                 {
-                    var separator = ",";
-                    var properties = string.Join(separator, partial.ToArray());
+                    const string separator = ",";
+                    var properties = string.Join(separator, partial);
                     final = "{" + properties + "}";
                     final = "{" + properties + "}";
                 }
                 }
                 else
                 else
                 {
                 {
                     var separator = ",\n" + _indent;
                     var separator = ",\n" + _indent;
-                    var properties = string.Join(separator, partial.ToArray());
+                    var properties = string.Join(separator, partial);
                     final = "{\n" + _indent + properties + "\n" + stepback + "}";
                     final = "{\n" + _indent + properties + "\n" + stepback + "}";
                 }
                 }
             }
             }