DefaultObjectConverter.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Threading;
  3. using Jint.Native;
  4. using Jint.Runtime;
  5. using Jint.Runtime.Interop;
  6. namespace Jint
  7. {
  8. internal static class DefaultObjectConverter
  9. {
  10. private static Dictionary<Type, Func<Engine, object, JsValue>> _typeMappers = new()
  11. {
  12. { typeof(bool), (engine, v) => (bool)v ? JsBoolean.True : JsBoolean.False },
  13. { typeof(byte), (engine, v) => JsNumber.Create((byte)v) },
  14. { typeof(char), (engine, v) => JsString.Create((char)v) },
  15. { typeof(DateTime), (engine, v) => engine.Realm.Intrinsics.Date.Construct((DateTime)v) },
  16. { typeof(DateTimeOffset), (engine, v) => engine.Realm.Intrinsics.Date.Construct((DateTimeOffset)v) },
  17. { typeof(decimal), (engine, v) => (JsValue)(double)(decimal)v },
  18. { typeof(double), (engine, v) => (JsValue)(double)v },
  19. { typeof(short), (engine, v) => JsNumber.Create((short)v) },
  20. { typeof(int), (engine, v) => JsNumber.Create((int)v) },
  21. { typeof(long), (engine, v) => (JsValue)(long)v },
  22. { typeof(sbyte), (engine, v) => JsNumber.Create((sbyte)v) },
  23. { typeof(float), (engine, v) => (JsValue)(float)v },
  24. { typeof(string), (engine, v) => JsString.Create((string)v) },
  25. { typeof(ushort), (engine, v) => JsNumber.Create((ushort)v) },
  26. { typeof(uint), (engine, v) => JsNumber.Create((uint)v) },
  27. { typeof(ulong), (engine, v) => JsNumber.Create((ulong)v) },
  28. {
  29. typeof(System.Text.RegularExpressions.Regex),
  30. (engine, v) => engine.Realm.Intrinsics.RegExp.Construct((System.Text.RegularExpressions.Regex)v, ((System.Text.RegularExpressions.Regex)v).ToString(), "")
  31. }
  32. };
  33. public static bool TryConvert(Engine engine, object value, [NotNullWhen(true)] out JsValue? result)
  34. {
  35. result = null;
  36. var valueType = value.GetType();
  37. var typeMappers = _typeMappers;
  38. if (typeMappers.TryGetValue(valueType, out var typeMapper))
  39. {
  40. result = typeMapper(engine, value);
  41. }
  42. else
  43. {
  44. if (value is Array a)
  45. {
  46. // racy, we don't care, worst case we'll catch up later
  47. Interlocked.CompareExchange(ref _typeMappers,
  48. new Dictionary<Type, Func<Engine, object, JsValue>>(typeMappers)
  49. {
  50. [valueType] = ConvertArray
  51. }, typeMappers);
  52. result = ConvertArray(engine, a);
  53. return result is not null;
  54. }
  55. if (value is IConvertible convertible && TryConvertConvertible(engine, convertible, out result))
  56. {
  57. return true;
  58. }
  59. if (value is Delegate d)
  60. {
  61. result = new DelegateWrapper(engine, d);
  62. }
  63. else
  64. {
  65. var t = value.GetType();
  66. if (!engine.Options.Interop.AllowSystemReflection
  67. && t.Namespace?.StartsWith("System.Reflection") == true)
  68. {
  69. const string message = "Cannot access System.Reflection namespace, check Engine's interop options";
  70. ExceptionHelper.ThrowInvalidOperationException(message);
  71. }
  72. if (t.IsEnum)
  73. {
  74. var ut = Enum.GetUnderlyingType(t);
  75. if (ut == typeof(ulong))
  76. {
  77. result = JsNumber.Create(Convert.ToDouble(value));
  78. }
  79. else
  80. {
  81. if (ut == typeof(uint) || ut == typeof(long))
  82. {
  83. result = JsNumber.Create(Convert.ToInt64(value));
  84. }
  85. else
  86. {
  87. result = JsNumber.Create(Convert.ToInt32(value));
  88. }
  89. }
  90. }
  91. else
  92. {
  93. // check global cache, have we already wrapped the value?
  94. if (engine._objectWrapperCache.TryGetValue(value, out var cached))
  95. {
  96. result = cached;
  97. }
  98. else
  99. {
  100. var wrapped = engine.Options.Interop.WrapObjectHandler.Invoke(engine, value);
  101. result = wrapped;
  102. if (engine.Options.Interop.TrackObjectWrapperIdentity && wrapped is not null)
  103. {
  104. engine._objectWrapperCache.Add(value, wrapped);
  105. }
  106. }
  107. }
  108. // if no known type could be guessed, use the default of wrapping using using ObjectWrapper.
  109. }
  110. }
  111. return result is not null;
  112. }
  113. private static bool TryConvertConvertible(Engine engine, IConvertible convertible, [NotNullWhen(true)] out JsValue? result)
  114. {
  115. result = convertible.GetTypeCode() switch
  116. {
  117. TypeCode.Boolean => convertible.ToBoolean(engine.Options.Culture) ? JsBoolean.True : JsBoolean.False,
  118. TypeCode.Byte => JsNumber.Create(convertible.ToByte(engine.Options.Culture)),
  119. TypeCode.Char => JsString.Create(convertible.ToChar(engine.Options.Culture)),
  120. TypeCode.Double => JsNumber.Create(convertible.ToDouble(engine.Options.Culture)),
  121. TypeCode.SByte => JsNumber.Create(convertible.ToSByte(engine.Options.Culture)),
  122. TypeCode.Int16 => JsNumber.Create(convertible.ToInt16(engine.Options.Culture)),
  123. TypeCode.Int32 => JsNumber.Create(convertible.ToInt32(engine.Options.Culture)),
  124. TypeCode.UInt16 => JsNumber.Create(convertible.ToUInt16(engine.Options.Culture)),
  125. TypeCode.Int64 => JsNumber.Create(convertible.ToInt64(engine.Options.Culture)),
  126. TypeCode.Single => JsNumber.Create(convertible.ToSingle(engine.Options.Culture)),
  127. TypeCode.String => JsString.Create(convertible.ToString(engine.Options.Culture)),
  128. TypeCode.UInt32 => JsNumber.Create(convertible.ToUInt32(engine.Options.Culture)),
  129. TypeCode.UInt64 => JsNumber.Create(convertible.ToUInt64(engine.Options.Culture)),
  130. TypeCode.DateTime => engine.Realm.Intrinsics.Date.Construct(convertible.ToDateTime(engine.Options.Culture)),
  131. TypeCode.Decimal => JsNumber.Create(convertible.ToDecimal(engine.Options.Culture)),
  132. TypeCode.DBNull => JsValue.Null,
  133. TypeCode.Empty => JsValue.Null,
  134. _ => null
  135. };
  136. return result is not null;
  137. }
  138. private static JsValue ConvertArray(Engine e, object v)
  139. {
  140. var array = (Array) v;
  141. var arrayLength = (uint) array.Length;
  142. var values = new JsValue[arrayLength];
  143. for (uint i = 0; i < arrayLength; ++i)
  144. {
  145. values[i] = JsValue.FromObject(e, array.GetValue(i));
  146. }
  147. return new JsArray(e, values);
  148. }
  149. }
  150. }