InteropTests.Json.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. using System.Dynamic;
  2. using System.Text;
  3. using FluentAssertions;
  4. using Jint.Runtime.Interop;
  5. namespace Jint.Tests.PublicInterface;
  6. public partial class InteropTests
  7. {
  8. [Fact]
  9. public void EngineShouldStringifyAnExpandoObjectCorrectly()
  10. {
  11. var engine = new Engine();
  12. dynamic expando = new ExpandoObject();
  13. expando.foo = 5;
  14. expando.bar = "A string";
  15. engine.SetValue(nameof(expando), expando);
  16. var result = engine.Evaluate($"JSON.stringify({nameof(expando)})").AsString();
  17. Assert.Equal("{\"foo\":5,\"bar\":\"A string\"}", result);
  18. }
  19. [Fact]
  20. public void EngineShouldStringifyAnExpandoObjectWithValuesCorrectly()
  21. {
  22. // https://github.com/sebastienros/jint/issues/995
  23. var engine = new Engine();
  24. dynamic expando = new ExpandoObject();
  25. expando.Values = 1;
  26. engine.SetValue("expando", expando);
  27. Assert.Equal("{\"Values\":1}", engine.Evaluate($"JSON.stringify(expando)").AsString());
  28. }
  29. [Fact]
  30. public void EngineShouldStringifyAnJObjectArrayWithValuesCorrectly()
  31. {
  32. //https://github.com/OrchardCMS/OrchardCore/issues/10648
  33. var engine = new Engine();
  34. var queryResults = new List<dynamic>();
  35. queryResults.Add(new { Text = "Text1", Value = 1 });
  36. queryResults.Add(new { Text = "Text2", Value = 2 });
  37. engine.SetValue("testSubject", queryResults.ToArray());
  38. var fromEngine2 = engine.Evaluate("return JSON.stringify(testSubject);");
  39. var result2 = fromEngine2.ToString();
  40. Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result2);
  41. }
  42. [Fact]
  43. public void EngineShouldStringifyDynamicObjectListWithValuesCorrectly()
  44. {
  45. var engine = new Engine();
  46. var source = new dynamic[] { new { Text = "Text1", Value = 1 }, new { Text = "Text2", Value = 2 } };
  47. var objects = source.ToList();
  48. engine.SetValue("testSubject", objects);
  49. var fromEngine = engine.Evaluate("return JSON.stringify(testSubject);");
  50. var result = fromEngine.ToString();
  51. Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result);
  52. }
  53. [Fact]
  54. public void EngineShouldStringifyDynamicObjectArrayWithValuesCorrectly()
  55. {
  56. var engine = new Engine();
  57. var source = new dynamic[] { new { Text = "Text1", Value = 1 }, new { Text = "Text2", Value = 2 } };
  58. engine.SetValue("testSubject", source.AsEnumerable());
  59. var fromEngine = engine.Evaluate("return JSON.stringify(testSubject);");
  60. var result = fromEngine.ToString();
  61. Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result);
  62. }
  63. [Fact]
  64. public void CanStringifyTimeSpanUsingCustomToJsonHook()
  65. {
  66. var engine = new Engine(options =>
  67. {
  68. options.Interop.MemberAccessor = (e, target, member) =>
  69. {
  70. // custom serializer hook for known object types
  71. if (member == "toJSON" && target is TimeSpan)
  72. {
  73. return new ClrFunction(e, "toJSON", (thisObject, _) =>
  74. {
  75. // here could check other types too
  76. var wrappedInstance = ((IObjectWrapper) thisObject).Target;
  77. return wrappedInstance.ToString();
  78. });
  79. }
  80. return null;
  81. };
  82. });
  83. var expected = Newtonsoft.Json.JsonConvert.SerializeObject(TimeSpan.FromSeconds(3));
  84. engine.SetValue("TimeSpan", TypeReference.CreateTypeReference<TimeSpan>(engine));
  85. var value = engine.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3));");
  86. Assert.Equal(expected, value);
  87. }
  88. [Fact]
  89. public void CanStringifyUsingSerializeToJson()
  90. {
  91. object testObject = new { Foo = "bar", FooBar = new { Foo = 123.45, Foobar = new DateTime(2022, 7, 16, 0, 0, 0, DateTimeKind.Utc) } };
  92. // without interop
  93. var e = new Engine();
  94. e.SetValue("TimeSpan", typeof(TimeSpan));
  95. #if NETFRAMEWORK
  96. e.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3))").AsString().Should().Be("""{"Ticks":30000000,"Days":0,"Hours":0,"Milliseconds":0,"Minutes":0,"Seconds":3,"TotalDays":0.00003472222222222222,"TotalHours":0.0008333333333333333,"TotalMilliseconds":3000,"TotalMinutes":0.05,"TotalSeconds":3}""");
  97. #else
  98. e.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3))").AsString().Should().Be("""{"Ticks":30000000,"Days":0,"Hours":0,"Milliseconds":0,"Microseconds":0,"Nanoseconds":0,"Minutes":0,"Seconds":3,"TotalDays":0.00003472222222222222,"TotalHours":0.0008333333333333334,"TotalMilliseconds":3000,"TotalMicroseconds":3000000,"TotalNanoseconds":3000000000,"TotalMinutes":0.05,"TotalSeconds":3}""");
  99. #endif
  100. e.SetValue("TestObject", testObject);
  101. e.Evaluate("JSON.stringify(TestObject)").AsString().Should().Be("""{"Foo":"bar","FooBar":{"Foo":123.45,"Foobar":"2022-07-16T00:00:00.000Z"}}""");
  102. // interop using Newtonsoft serializer, for example with snake case naming
  103. var engine = new Engine(options =>
  104. {
  105. options.Interop.SerializeToJson = Serialize;
  106. });
  107. engine.SetValue("TimeSpan", TypeReference.CreateTypeReference<TimeSpan>(engine));
  108. engine.SetValue("TestObject", testObject);
  109. var expected = Serialize(TimeSpan.FromSeconds(3), string.Empty);
  110. var actual = engine.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3));");
  111. actual.AsString().Should().Be(expected);
  112. expected = Serialize(testObject, string.Empty);
  113. actual = engine.Evaluate("JSON.stringify(TestObject)");
  114. actual.AsString().Should().Be(expected);
  115. actual = engine.Evaluate("JSON.stringify({ nestedValue: TestObject })");
  116. actual.AsString().Should().Be($$"""{"nestedValue":{{expected}}}""");
  117. static string Serialize(object o, string space, string currentIndent = null)
  118. {
  119. var settings = new Newtonsoft.Json.JsonSerializerSettings
  120. {
  121. ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver
  122. {
  123. NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy()
  124. }
  125. };
  126. return Newtonsoft.Json.JsonConvert.SerializeObject(o, settings);
  127. }
  128. }
  129. [Fact]
  130. public void CanStringifyUsingSerializeToJsonWithIndentation()
  131. {
  132. object testObject = new { Foo = "bar", FooBar = new { Foo = 123.45, Array = Array.Empty<int>() } };
  133. var e = new Engine(o => o.Interop.SerializeToJson = SerializeIndentation);
  134. e.SetValue("TestObject", testObject);
  135. e.Evaluate("JSON.stringify(TestObject, null, 4)").AsString().Should().Be(
  136. """
  137. {
  138. "foo": "bar",
  139. "foo_bar": {
  140. "foo": 123.45,
  141. "array": []
  142. }
  143. }
  144. """
  145. );
  146. e.Evaluate("JSON.stringify({ nestedValue: TestObject }, null, 4)").AsString().Should().Be(
  147. """
  148. {
  149. "nestedValue": {
  150. "foo": "bar",
  151. "foo_bar": {
  152. "foo": 123.45,
  153. "array": []
  154. }
  155. }
  156. }
  157. """.Replace("\r\n", "\n")
  158. );
  159. static string SerializeIndentation(object o, string space, string currentIndent)
  160. {
  161. var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create(new Newtonsoft.Json.JsonSerializerSettings
  162. {
  163. ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver
  164. {
  165. NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy()
  166. }
  167. });
  168. var sb = new StringBuilder(256);
  169. var sw = new StringWriter(sb);
  170. using var jsonWriter = string.IsNullOrEmpty(space)
  171. ? new Newtonsoft.Json.JsonTextWriter(sw)
  172. : new(sw)
  173. {
  174. Formatting = Newtonsoft.Json.Formatting.Indented,
  175. Indentation = space.Length,
  176. IndentChar = space[0] // assuming `space` only contains one kind of character
  177. };
  178. jsonSerializer.Serialize(jsonWriter, o);
  179. if (string.IsNullOrEmpty(currentIndent))
  180. {
  181. return sw.ToString();
  182. }
  183. else
  184. {
  185. var lines = sw.ToString().Split('\n');
  186. var stringBuilder = new StringBuilder();
  187. for (var i = 0; i < lines.Length; i++)
  188. {
  189. if (i == 0)
  190. {
  191. stringBuilder.AppendLine(lines[i]);
  192. }
  193. else
  194. {
  195. stringBuilder.AppendLine(currentIndent + lines[i]);
  196. }
  197. }
  198. return stringBuilder.ToString().Replace("\r", string.Empty).TrimEnd('\n');
  199. }
  200. }
  201. }
  202. }