DefaultObjectConverter.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Globalization;
  3. using System.Runtime.CompilerServices;
  4. using System.Threading;
  5. using Jint.Native;
  6. using Jint.Native.Object;
  7. using Jint.Runtime;
  8. using Jint.Runtime.Interop;
  9. namespace Jint;
  10. internal static class DefaultObjectConverter
  11. {
  12. private static Dictionary<Type, Func<Engine, object, JsValue>> _typeMappers = new()
  13. {
  14. { typeof(bool), (engine, v) => (bool)v ? JsBoolean.True : JsBoolean.False },
  15. { typeof(byte), (engine, v) => JsNumber.Create((byte)v) },
  16. { typeof(char), (engine, v) => JsString.Create((char)v) },
  17. { typeof(DateTime), (engine, v) => engine.Realm.Intrinsics.Date.Construct((DateTime)v) },
  18. { typeof(DateTimeOffset), (engine, v) => engine.Realm.Intrinsics.Date.Construct((DateTimeOffset)v) },
  19. { typeof(decimal), (engine, v) => (JsValue)(double)(decimal)v },
  20. { typeof(double), (engine, v) => (JsValue)(double)v },
  21. { typeof(short), (engine, v) => JsNumber.Create((short)v) },
  22. { typeof(int), (engine, v) => JsNumber.Create((int)v) },
  23. { typeof(long), (engine, v) => (JsValue)(long)v },
  24. { typeof(sbyte), (engine, v) => JsNumber.Create((sbyte)v) },
  25. { typeof(float), (engine, v) => (JsValue)(float)v },
  26. { typeof(string), (engine, v) => JsString.Create((string)v) },
  27. { typeof(ushort), (engine, v) => JsNumber.Create((ushort)v) },
  28. { typeof(uint), (engine, v) => JsNumber.Create((uint)v) },
  29. { typeof(ulong), (engine, v) => JsNumber.Create((ulong)v) },
  30. {
  31. typeof(System.Text.RegularExpressions.Regex),
  32. (engine, v) => engine.Realm.Intrinsics.RegExp.Construct((System.Text.RegularExpressions.Regex)v, ((System.Text.RegularExpressions.Regex)v).ToString(), "")
  33. }
  34. };
  35. public static bool TryConvert(Engine engine, object value, Type? type, [NotNullWhen(true)] out JsValue? result)
  36. {
  37. result = null;
  38. Type valueType = ObjectWrapper.GetClrType(value, type);
  39. var typeMappers = _typeMappers;
  40. if (typeMappers.TryGetValue(valueType, out var typeMapper))
  41. {
  42. result = typeMapper(engine, value);
  43. }
  44. else
  45. {
  46. if (value is Array a)
  47. {
  48. // racy, we don't care, worst case we'll catch up later
  49. Interlocked.CompareExchange(ref _typeMappers,
  50. new Dictionary<Type, Func<Engine, object, JsValue>>(typeMappers)
  51. {
  52. [valueType] = ConvertArray
  53. }, typeMappers);
  54. result = ConvertArray(engine, a);
  55. return result is not null;
  56. }
  57. if (value is IConvertible convertible && TryConvertConvertible(engine, convertible, out result))
  58. {
  59. return true;
  60. }
  61. if (value is Delegate d)
  62. {
  63. result = new DelegateWrapper(engine, d);
  64. return result is not null;
  65. }
  66. if ((engine.Options.ExperimentalFeatures & ExperimentalFeature.TaskInterop) != ExperimentalFeature.None)
  67. {
  68. if (value is Task task)
  69. {
  70. result = JsValue.ConvertAwaitableToPromise(engine, task);
  71. return result is not null;
  72. }
  73. #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
  74. if (value is ValueTask valueTask)
  75. {
  76. result = JsValue.ConvertAwaitableToPromise(engine, valueTask);
  77. return result is not null;
  78. }
  79. #endif
  80. }
  81. #if NET8_0_OR_GREATER
  82. if (value is System.Text.Json.Nodes.JsonValue jsonValue)
  83. {
  84. result = ConvertSystemTextJsonValue(engine, jsonValue);
  85. return result is not null;
  86. }
  87. #endif
  88. var t = value.GetType();
  89. if (!engine.Options.Interop.AllowSystemReflection
  90. && t.Namespace?.StartsWith("System.Reflection", StringComparison.Ordinal) == true)
  91. {
  92. const string Message = "Cannot access System.Reflection namespace, check Engine's interop options";
  93. Throw.InvalidOperationException(Message);
  94. }
  95. if (t.IsEnum)
  96. {
  97. var ut = Enum.GetUnderlyingType(t);
  98. if (ut == typeof(ulong))
  99. {
  100. result = JsNumber.Create(Convert.ToDouble(value, CultureInfo.InvariantCulture));
  101. }
  102. else
  103. {
  104. if (ut == typeof(uint) || ut == typeof(long))
  105. {
  106. result = JsNumber.Create(Convert.ToInt64(value, CultureInfo.InvariantCulture));
  107. }
  108. else
  109. {
  110. result = JsNumber.Create(Convert.ToInt32(value, CultureInfo.InvariantCulture));
  111. }
  112. }
  113. }
  114. else
  115. {
  116. // check global cache, have we already wrapped the value?
  117. if (engine._objectWrapperCache?.TryGetValue(value, out var cached) == true)
  118. {
  119. result = cached;
  120. }
  121. else
  122. {
  123. var wrapped = engine.Options.Interop.WrapObjectHandler.Invoke(engine, value, type);
  124. if (ReferenceEquals(wrapped?.GetPrototypeOf(), engine.Realm.Intrinsics.Object.PrototypeObject)
  125. && engine._typeReferences?.TryGetValue(t, out var typeReference) == true)
  126. {
  127. wrapped.SetPrototypeOf(typeReference);
  128. }
  129. result = wrapped;
  130. if (engine.Options.Interop.TrackObjectWrapperIdentity && wrapped is not null)
  131. {
  132. engine._objectWrapperCache ??= new ConditionalWeakTable<object, ObjectInstance>();
  133. engine._objectWrapperCache.Add(value, wrapped);
  134. }
  135. }
  136. }
  137. // if no known type could be guessed, use the default of wrapping using ObjectWrapper
  138. }
  139. return result is not null;
  140. }
  141. #if NET8_0_OR_GREATER
  142. private static JsValue? ConvertSystemTextJsonValue(Engine engine, System.Text.Json.Nodes.JsonNode value)
  143. {
  144. return value.GetValueKind() switch
  145. {
  146. System.Text.Json.JsonValueKind.Object => JsValue.FromObject(engine, value),
  147. System.Text.Json.JsonValueKind.Array => JsValue.FromObject(engine, value),
  148. System.Text.Json.JsonValueKind.String => JsString.Create(value.ToString()),
  149. #pragma warning disable IL2026, IL3050
  150. System.Text.Json.JsonValueKind.Number => ((System.Text.Json.Nodes.JsonValue) value).TryGetValue<int>(out var intValue)
  151. ? JsNumber.Create(intValue)
  152. : System.Text.Json.JsonSerializer.Deserialize<double>(value),
  153. #pragma warning restore IL2026, IL3050
  154. System.Text.Json.JsonValueKind.True => JsBoolean.True,
  155. System.Text.Json.JsonValueKind.False => JsBoolean.False,
  156. System.Text.Json.JsonValueKind.Undefined => JsValue.Undefined,
  157. System.Text.Json.JsonValueKind.Null => JsValue.Null,
  158. _ => null,
  159. };
  160. }
  161. #endif
  162. private static bool TryConvertConvertible(Engine engine, IConvertible convertible, [NotNullWhen(true)] out JsValue? result)
  163. {
  164. result = convertible.GetTypeCode() switch
  165. {
  166. TypeCode.Boolean => convertible.ToBoolean(engine.Options.Culture) ? JsBoolean.True : JsBoolean.False,
  167. TypeCode.Byte => JsNumber.Create(convertible.ToByte(engine.Options.Culture)),
  168. TypeCode.Char => JsString.Create(convertible.ToChar(engine.Options.Culture)),
  169. TypeCode.Double => JsNumber.Create(convertible.ToDouble(engine.Options.Culture)),
  170. TypeCode.SByte => JsNumber.Create(convertible.ToSByte(engine.Options.Culture)),
  171. TypeCode.Int16 => JsNumber.Create(convertible.ToInt16(engine.Options.Culture)),
  172. TypeCode.Int32 => JsNumber.Create(convertible.ToInt32(engine.Options.Culture)),
  173. TypeCode.UInt16 => JsNumber.Create(convertible.ToUInt16(engine.Options.Culture)),
  174. TypeCode.Int64 => JsNumber.Create(convertible.ToInt64(engine.Options.Culture)),
  175. TypeCode.Single => JsNumber.Create(convertible.ToSingle(engine.Options.Culture)),
  176. TypeCode.String => JsString.Create(convertible.ToString(engine.Options.Culture)),
  177. TypeCode.UInt32 => JsNumber.Create(convertible.ToUInt32(engine.Options.Culture)),
  178. TypeCode.UInt64 => JsNumber.Create(convertible.ToUInt64(engine.Options.Culture)),
  179. TypeCode.DateTime => engine.Realm.Intrinsics.Date.Construct(convertible.ToDateTime(engine.Options.Culture)),
  180. TypeCode.Decimal => JsNumber.Create(convertible.ToDecimal(engine.Options.Culture)),
  181. TypeCode.DBNull => JsValue.Null,
  182. TypeCode.Empty => JsValue.Null,
  183. _ => null
  184. };
  185. return result is not null;
  186. }
  187. private static JsArray ConvertArray(Engine e, object v)
  188. {
  189. var array = (Array) v;
  190. var arrayLength = (uint) array.Length;
  191. var values = new JsValue[arrayLength];
  192. for (uint i = 0; i < arrayLength; ++i)
  193. {
  194. values[i] = JsValue.FromObject(e, array.GetValue(i));
  195. }
  196. return new JsArray(e, values);
  197. }
  198. }