diff --git a/Jint.Tests.PublicInterface/InteropTests.SystemTextJson.cs b/Jint.Tests.PublicInterface/InteropTests.SystemTextJson.cs
index ef36ef549b..83aaa54405 100644
--- a/Jint.Tests.PublicInterface/InteropTests.SystemTextJson.cs
+++ b/Jint.Tests.PublicInterface/InteropTests.SystemTextJson.cs
@@ -1,4 +1,3 @@
-using System.Reflection;
using System.Text.Json.Nodes;
using Jint.Native;
using Jint.Runtime.Interop;
@@ -8,6 +7,12 @@ namespace Jint.Tests.PublicInterface;
public sealed class SystemTextJsonValueConverter : IObjectConverter
{
+ public static readonly SystemTextJsonValueConverter Instance = new();
+
+ private SystemTextJsonValueConverter()
+ {
+ }
+
public bool TryConvert(Engine engine, object value, out JsValue result)
{
if (value is JsonValue jsonValue)
@@ -90,6 +95,9 @@ public void AccessingJsonNodeShouldWork()
var engine = new Engine(options =>
{
+#if !NET8_0_OR_GREATER
+ // Jint doesn't know about the types statically as they are not part of the out-of-the-box experience
+
// make JsonArray behave like JS array
options.Interop.WrapObjectHandler = static (e, target, type) =>
{
@@ -103,13 +111,14 @@ public void AccessingJsonNodeShouldWork()
return ObjectWrapper.Create(e, target);
};
- options.AddObjectConverter(new SystemTextJsonValueConverter());
+ options.AddObjectConverter(SystemTextJsonValueConverter.Instance);
+
// we cannot access this[string] with anything else than JsonObject, otherwise itw will throw
options.Interop.TypeResolver = new TypeResolver
{
MemberFilter = static info =>
{
- if (info.ReflectedType != typeof(JsonObject) && info.Name == "Item" && info is PropertyInfo p)
+ if (info.ReflectedType != typeof(JsonObject) && info.Name == "Item" && info is System.Reflection.PropertyInfo p)
{
var parameters = p.GetIndexParameters();
return parameters.Length != 1 || parameters[0].ParameterType != typeof(string);
@@ -118,6 +127,7 @@ public void AccessingJsonNodeShouldWork()
return true;
}
};
+#endif
});
engine
diff --git a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj
index c9a9500625..d355919d43 100644
--- a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj
+++ b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj
@@ -27,7 +27,7 @@
-
+
diff --git a/Jint/Options.cs b/Jint/Options.cs
index e4ab735e8c..b1079d1232 100644
--- a/Jint/Options.cs
+++ b/Jint/Options.cs
@@ -355,6 +355,11 @@ public class InteropOptions
/// Defaults to .
///
public DateTimeKind DateTimeKind { get; set; } = DateTimeKind.Utc;
+
+ ///
+ /// Should the Array prototype be attached instead of Object prototype to the wrapped interop objects when type looks suitable. Defaults to true.
+ ///
+ public bool AttachArrayPrototype { get; set; } = true;
}
public class ConstraintOptions
diff --git a/Jint/Runtime/Interop/DefaultObjectConverter.cs b/Jint/Runtime/Interop/DefaultObjectConverter.cs
index 4eb726c339..2b0be92e3a 100644
--- a/Jint/Runtime/Interop/DefaultObjectConverter.cs
+++ b/Jint/Runtime/Interop/DefaultObjectConverter.cs
@@ -86,6 +86,14 @@ public static bool TryConvert(Engine engine, object value, Type? type, [NotNullW
}
#endif
+#if NET8_0_OR_GREATER
+ if (value is System.Text.Json.Nodes.JsonValue jsonValue)
+ {
+ result = ConvertSystemTextJsonValue(engine, jsonValue);
+ return result is not null;
+ }
+#endif
+
var t = value.GetType();
if (!engine.Options.Interop.AllowSystemReflection
@@ -148,6 +156,24 @@ public static bool TryConvert(Engine engine, object value, Type? type, [NotNullW
return result is not null;
}
+#if NET8_0_OR_GREATER
+ private static JsValue? ConvertSystemTextJsonValue(Engine engine, System.Text.Json.Nodes.JsonValue value)
+ {
+ return value.GetValueKind() switch
+ {
+ System.Text.Json.JsonValueKind.Object => JsValue.FromObject(engine, value),
+ System.Text.Json.JsonValueKind.Array => JsValue.FromObject(engine, value),
+ System.Text.Json.JsonValueKind.String => JsString.Create(value.ToString()),
+ System.Text.Json.JsonValueKind.Number => value.TryGetValue(out var doubleValue) ? JsNumber.Create(doubleValue) : JsValue.Undefined,
+ System.Text.Json.JsonValueKind.True => JsBoolean.True,
+ System.Text.Json.JsonValueKind.False => JsBoolean.False,
+ System.Text.Json.JsonValueKind.Undefined => JsValue.Undefined,
+ System.Text.Json.JsonValueKind.Null => JsValue.Null,
+ _ => null
+ };
+ }
+#endif
+
private static bool TryConvertConvertible(Engine engine, IConvertible convertible, [NotNullWhen(true)] out JsValue? result)
{
result = convertible.GetTypeCode() switch
diff --git a/Jint/Runtime/Interop/DefaultTypeConverter.cs b/Jint/Runtime/Interop/DefaultTypeConverter.cs
index 6ddd7cfb86..7e1a376790 100644
--- a/Jint/Runtime/Interop/DefaultTypeConverter.cs
+++ b/Jint/Runtime/Interop/DefaultTypeConverter.cs
@@ -56,7 +56,8 @@ public DefaultTypeConverter(Engine engine)
return converted;
}
- public virtual bool TryConvert(object? value,
+ public virtual bool TryConvert(
+ object? value,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields)] Type type,
IFormatProvider formatProvider,
[NotNullWhen(true)] out object? converted)
diff --git a/Jint/Runtime/Interop/MethodInfoFunction.cs b/Jint/Runtime/Interop/MethodInfoFunction.cs
index f1b1da7a94..9e7218f33b 100644
--- a/Jint/Runtime/Interop/MethodInfoFunction.cs
+++ b/Jint/Runtime/Interop/MethodInfoFunction.cs
@@ -276,9 +276,7 @@ private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, MethodDescriptor me
return jsArguments;
}
- var jsArray = Engine.Realm.Intrinsics.Array.Construct(Arguments.Empty);
- Engine.Realm.Intrinsics.Array.PrototypeObject.Push(jsArray, argsToTransform);
-
+ var jsArray = new JsArray(_engine, argsToTransform);
var newArgumentsCollection = new JsValue[nonParamsArgumentsCount + 1];
for (var j = 0; j < nonParamsArgumentsCount; ++j)
{
diff --git a/Jint/Runtime/Interop/ObjectWrapper.cs b/Jint/Runtime/Interop/ObjectWrapper.cs
index 17b05c6100..9ea5025d30 100644
--- a/Jint/Runtime/Interop/ObjectWrapper.cs
+++ b/Jint/Runtime/Interop/ObjectWrapper.cs
@@ -31,12 +31,19 @@ public ObjectWrapper(
Target = obj;
ClrType = GetClrType(obj, type);
_typeDescriptor = TypeDescriptor.Get(ClrType);
+
if (_typeDescriptor.LengthProperty is not null)
{
// create a forwarder to produce length from Count or Length if one of them is present
var functionInstance = new ClrFunction(engine, "length", GetLength);
var descriptor = new GetSetPropertyDescriptor(functionInstance, Undefined, PropertyFlag.Configurable);
SetProperty(KnownKeys.Length, descriptor);
+
+ if (_typeDescriptor.IsArrayLike && engine.Options.Interop.AttachArrayPrototype)
+ {
+ // if we have array-like object, we can attach array prototype
+ SetPrototypeOf(engine.Intrinsics.Array.PrototypeObject);
+ }
}
}
@@ -45,7 +52,18 @@ public ObjectWrapper(
///
public static ObjectInstance Create(Engine engine, object obj, Type? type = null)
#pragma warning disable CS0618 // Type or member is obsolete
- => new ObjectWrapper(engine, obj, type);
+ {
+
+#if NET8_0_OR_GREATER
+ if (type == typeof(System.Text.Json.Nodes.JsonNode))
+ {
+ // we need to always expose the actual type instead of the type nodes provide
+ type = obj.GetType();
+ }
+#endif
+
+ return new ObjectWrapper(engine, obj, type);
+ }
#pragma warning restore CS0618 // Type or member is obsolete
public object Target { get; }
diff --git a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs
index 5a86c5bf1a..d64670af12 100644
--- a/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs
+++ b/Jint/Runtime/Interop/Reflection/IndexerAccessor.cs
@@ -48,46 +48,13 @@ internal static bool TryFindIndexer(
integerKey = intKeyTemp;
}
- IndexerAccessor? ComposeIndexerFactory(PropertyInfo candidate, Type paramType)
- {
- object? key = null;
- // int key is quite common case
- if (paramType == typeof(int) && integerKey is not null)
- {
- key = integerKey;
- }
- else
- {
- engine.TypeConverter.TryConvert(propertyName, paramType, CultureInfo.InvariantCulture, out key);
- }
-
- if (key is not null)
- {
- // the key can be converted for this indexer
- var indexerProperty = candidate;
- // get contains key method to avoid index exception being thrown in dictionaries
- paramTypeArray[0] = paramType;
- var containsKeyMethod = targetType.GetMethod(nameof(IDictionary.ContainsKey), paramTypeArray);
- if (containsKeyMethod is null && targetType.IsAssignableFrom(typeof(IDictionary)))
- {
- paramTypeArray[0] = typeof(object);
- containsKeyMethod = targetType.GetMethod(nameof(IDictionary.Contains), paramTypeArray);
- }
-
- return new IndexerAccessor(indexerProperty, containsKeyMethod, key);
- }
-
- // the key type doesn't work for this indexer
- return null;
- }
-
var filter = new Func(m => engine.Options.Interop.TypeResolver.Filter(engine, m));
// default indexer wins
var descriptor = TypeDescriptor.Get(targetType);
- if (descriptor.IntegerIndexerProperty is not null && filter(descriptor.IntegerIndexerProperty))
+ if (descriptor.IntegerIndexerProperty is not null && !filter(descriptor.IntegerIndexerProperty))
{
- indexerAccessor = ComposeIndexerFactory(descriptor.IntegerIndexerProperty, typeof(int));
+ indexerAccessor = ComposeIndexerFactory(engine, targetType, descriptor.IntegerIndexerProperty, paramType: typeof(int), propertyName, integerKey, paramTypeArray);
if (indexerAccessor != null)
{
indexer = descriptor.IntegerIndexerProperty;
@@ -113,7 +80,7 @@ internal static bool TryFindIndexer(
if (candidate.GetGetMethod() != null || candidate.GetSetMethod() != null)
{
var paramType = indexParameters[0].ParameterType;
- indexerAccessor = ComposeIndexerFactory(candidate, paramType);
+ indexerAccessor = ComposeIndexerFactory(engine, targetType, candidate, paramType, propertyName, integerKey, paramTypeArray);
if (indexerAccessor != null)
{
if (paramType != typeof(string) || integerKey is null)
@@ -145,6 +112,62 @@ internal static bool TryFindIndexer(
return false;
}
+ private static IndexerAccessor? ComposeIndexerFactory(
+ Engine engine,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] Type targetType,
+ PropertyInfo candidate,
+ Type paramType,
+ string propertyName,
+ int? integerKey,
+ Type[] paramTypeArray)
+ {
+ // check for known incompatible types
+#if NET8_0_OR_GREATER
+ if (typeof(System.Text.Json.Nodes.JsonNode).IsAssignableFrom(targetType)
+ && (targetType != typeof(System.Text.Json.Nodes.JsonArray) || paramType != typeof(int))
+ && (targetType != typeof(System.Text.Json.Nodes.JsonObject) || paramType != typeof(string)))
+ {
+ // we cannot access this[string] with anything else than JsonObject, otherwise itw will throw
+ // we cannot access this[int] with anything else than JsonArray, otherwise itw will throw
+ return null;
+ }
+#endif
+
+ object? key = null;
+ // int key is quite common case
+ if (paramType == typeof(int))
+ {
+ if (integerKey is not null)
+ {
+ key = integerKey;
+ }
+ }
+ else
+ {
+ engine.TypeConverter.TryConvert(propertyName, paramType, CultureInfo.InvariantCulture, out key);
+ }
+
+ if (key is not null)
+ {
+ // the key can be converted for this indexer
+ var indexerProperty = candidate;
+ // get contains key method to avoid index exception being thrown in dictionaries
+ paramTypeArray[0] = paramType;
+ var containsKeyMethod = targetType.GetMethod(nameof(IDictionary.ContainsKey), paramTypeArray);
+ if (containsKeyMethod is null && targetType.IsAssignableFrom(typeof(IDictionary)))
+ {
+ paramTypeArray[0] = typeof(object);
+ containsKeyMethod = targetType.GetMethod(nameof(IDictionary.Contains), paramTypeArray);
+ }
+
+ return new IndexerAccessor(indexerProperty, containsKeyMethod, key);
+ }
+
+ // the key type doesn't work for this indexer
+ return null;
+ }
+
+
public override bool Readable => _indexer.CanRead;
public override bool Writable => _indexer.CanWrite;
@@ -157,7 +180,7 @@ internal static bool TryFindIndexer(
return null;
}
- object[] parameters = { _key };
+ object[] parameters = [_key];
if (_containsKey != null)
{