InteropTests.SystemTextJson.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using System.Text.Json.Nodes;
  2. using Jint.Native;
  3. using Jint.Runtime.Interop;
  4. using System.Text.Json;
  5. namespace Jint.Tests.PublicInterface;
  6. public partial class InteropTests
  7. {
  8. [Fact]
  9. public void ArrayPrototypeFindWithInteropJsonArray()
  10. {
  11. var engine = GetEngine();
  12. var array = new JsonArray { "A", "B", "C" };
  13. engine.SetValue("array", array);
  14. Assert.Equal(1, engine.Evaluate("array.findIndex((x) => x === 'B')"));
  15. Assert.Equal('B', engine.Evaluate("array.find((x) => x === 'B')"));
  16. }
  17. [Fact]
  18. public void ArrayPrototypePushWithInteropJsonArray()
  19. {
  20. var engine = GetEngine();
  21. var array = new JsonArray { "A", "B", "C" };
  22. engine.SetValue("array", array);
  23. engine.Evaluate("array.push('D')");
  24. Assert.Equal(4, array.Count);
  25. Assert.Equal("D", array[3]?.ToString());
  26. Assert.Equal(3, engine.Evaluate("array.lastIndexOf('D')"));
  27. }
  28. [Fact]
  29. public void ArrayPrototypePopWithInteropJsonArray()
  30. {
  31. var engine = GetEngine();
  32. var array = new JsonArray { "A", "B", "C" };
  33. engine.SetValue("array", array);
  34. Assert.Equal(2, engine.Evaluate("array.lastIndexOf('C')"));
  35. Assert.Equal(3, array.Count);
  36. Assert.Equal("C", engine.Evaluate("array.pop()"));
  37. Assert.Equal(2, array.Count);
  38. Assert.Equal(-1, engine.Evaluate("array.lastIndexOf('C')"));
  39. }
  40. [Fact]
  41. public void AccessingJsonNodeShouldWork()
  42. {
  43. const string Json = """
  44. {
  45. "falseValue": false,
  46. "employees": {
  47. "trueValue": true,
  48. "falseValue": false,
  49. "number": 123.456,
  50. "zeroNumber": 0,
  51. "emptyString":"",
  52. "nullValue":null,
  53. "other": "abc",
  54. "type": "array",
  55. "value": [
  56. {
  57. "firstName": "John",
  58. "lastName": "Doe"
  59. },
  60. {
  61. "firstName": "Jane",
  62. "lastName": "Doe"
  63. }
  64. ]
  65. }
  66. }
  67. """;
  68. var variables = JsonNode.Parse(Json);
  69. var engine = GetEngine();
  70. engine
  71. .SetValue("falseValue", false)
  72. .SetValue("variables", variables)
  73. .Execute("""
  74. function populateFullName() {
  75. return variables['employees'].value.map(item => {
  76. var newItem =
  77. {
  78. "firstName": item.firstName,
  79. "lastName": item.lastName,
  80. "fullName": item.firstName + ' ' + item.lastName
  81. };
  82. return newItem;
  83. });
  84. }
  85. """);
  86. // reading data
  87. var result = engine.Evaluate("populateFullName()").AsArray();
  88. Assert.Equal((uint) 2, result.Length);
  89. Assert.Equal("John Doe", result[0].AsObject()["fullName"]);
  90. Assert.Equal("Jane Doe", result[1].AsObject()["fullName"]);
  91. Assert.True(engine.Evaluate("variables.employees.trueValue == true").AsBoolean());
  92. Assert.True(engine.Evaluate("variables.employees.number == 123.456").AsBoolean());
  93. Assert.True(engine.Evaluate("variables.employees.other == 'abc'").AsBoolean());
  94. // mutating data via JS
  95. engine.Evaluate("variables.employees.type = 'array2'");
  96. engine.Evaluate("variables.employees.value[0].firstName = 'Jake'");
  97. //Assert.Equal("array2", engine.Evaluate("variables['employees']['type']").ToString());
  98. result = engine.Evaluate("populateFullName()").AsArray();
  99. Assert.Equal((uint) 2, result.Length);
  100. Assert.Equal("Jake Doe", result[0].AsObject()["fullName"]);
  101. // Validate boolean value in the if condition.
  102. Assert.Equal(1, engine.Evaluate("if(!falseValue){ return 1 ;} else {return 0;}").AsNumber());
  103. Assert.Equal(1, engine.Evaluate("if(falseValue===false){ return 1 ;} else {return 0;}").AsNumber());
  104. Assert.True(engine.Evaluate("!variables.zeroNumber").AsBoolean());
  105. Assert.True(engine.Evaluate("!variables.emptyString").AsBoolean());
  106. Assert.True(engine.Evaluate("!variables.nullValue").AsBoolean());
  107. var result2 = engine.Evaluate("!variables.falseValue");
  108. var result3 = engine.Evaluate("!falseValue");
  109. var result4 = engine.Evaluate("variables.falseValue");
  110. var result5 = engine.Evaluate("falseValue");
  111. Assert.NotNull(result2);
  112. Assert.Equal(1, engine.Evaluate("if(variables.falseValue===false){ return 1 ;} else {return 0;}").AsNumber());
  113. Assert.Equal(1, engine.Evaluate("if(falseValue===variables.falseValue){ return 1 ;} else {return 0;}").AsNumber());
  114. Assert.Equal(1, engine.Evaluate("if(!variables.falseValue){ return 1 ;} else {return 0;}").AsNumber());
  115. Assert.Equal(1, engine.Evaluate("if(!variables.employees.falseValue){ return 1 ;} else {return 0;}").AsNumber());
  116. Assert.Equal(0, engine.Evaluate("if(!variables.employees.trueValue) return 1 ; else return 0;").AsNumber());
  117. // mutating original object that is wrapped inside the engine
  118. variables["employees"]["trueValue"] = false;
  119. variables["employees"]["number"] = 456.789;
  120. variables["employees"]["other"] = "def";
  121. variables["employees"]["type"] = "array";
  122. variables["employees"]["value"][0]["firstName"] = "John";
  123. Assert.Equal("array", engine.Evaluate("variables['employees']['type']").ToString());
  124. result = engine.Evaluate("populateFullName()").AsArray();
  125. Assert.Equal((uint) 2, result.Length);
  126. Assert.Equal("John Doe", result[0].AsObject()["fullName"]);
  127. Assert.True(engine.Evaluate("variables.employees.trueValue == false").AsBoolean());
  128. Assert.True(engine.Evaluate("variables.employees.number == 456.789").AsBoolean());
  129. Assert.True(engine.Evaluate("variables.employees.other == 'def'").AsBoolean());
  130. }
  131. [Fact]
  132. public void AccessingSystemTextJsonNumericTypes()
  133. {
  134. var engine = GetEngine();
  135. Assert.Equal(15, engine.SetValue("int", JsonValue.Create(15)).Evaluate("int"));
  136. Assert.Equal(15.0, engine.SetValue("double", JsonValue.Create(15.0)).Evaluate("double"));
  137. Assert.Equal(15f, engine.SetValue("float", JsonValue.Create(15.0f)).Evaluate("float"));
  138. }
  139. private static Engine GetEngine()
  140. {
  141. var engine = new Engine(options =>
  142. {
  143. #if !NET8_0_OR_GREATER
  144. // Jint doesn't know about the types statically as they are not part of the out-of-the-box experience
  145. options.AddObjectConverter(SystemTextJsonValueConverter.Instance);
  146. #endif
  147. });
  148. return engine;
  149. }
  150. }
  151. file sealed class SystemTextJsonValueConverter : IObjectConverter
  152. {
  153. public static readonly SystemTextJsonValueConverter Instance = new();
  154. private SystemTextJsonValueConverter()
  155. {
  156. }
  157. public bool TryConvert(Engine engine, object value, out JsValue result)
  158. {
  159. if (value is JsonValue jsonValue)
  160. {
  161. var valueKind = jsonValue.GetValueKind();
  162. result = valueKind switch
  163. {
  164. JsonValueKind.Object or JsonValueKind.Array => JsValue.FromObject(engine, jsonValue),
  165. JsonValueKind.String => jsonValue.ToString(),
  166. #pragma warning disable IL2026, IL3050
  167. JsonValueKind.Number => jsonValue.TryGetValue<int>(out var intValue) ? JsNumber.Create(intValue) : JsonSerializer.Deserialize<double>(jsonValue),
  168. #pragma warning restore IL2026, IL3050
  169. JsonValueKind.True => JsBoolean.True,
  170. JsonValueKind.False => JsBoolean.False,
  171. JsonValueKind.Undefined => JsValue.Undefined,
  172. JsonValueKind.Null => JsValue.Null,
  173. _ => JsValue.Undefined
  174. };
  175. return true;
  176. }
  177. result = JsValue.Undefined;
  178. return false;
  179. }
  180. }