diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 7e57f4b975..7b2a3efa04 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -2582,6 +2582,17 @@ public void Test2(DictionaryWrapper dictionaryObject) { Assert.Equal(1, Convert.ToInt32(dictionaryObject.Values["a"])); } + + public void Test3(Dictionary values) + { + Assert.Equal(1, Convert.ToInt32(values["a"])); + } + + public void Test4(Dictionary values = null) + { + Assert.NotNull(values); + Assert.Equal("world", values["value"]); + } } [Fact] @@ -2600,6 +2611,22 @@ public void ShouldBeAbleToPassDictionaryInObjectToMethod() engine.Evaluate("dictionaryTest.test2({ values: { a: 1 } });"); } + [Fact] + public void ShouldBeAbleToPassConcreteDictionaryToMethod() + { + var engine = new Engine(); + engine.SetValue("dictionaryTest", new DictionaryTest()); + engine.Evaluate("dictionaryTest.test3({ a: 1 });"); + } + + [Fact] + public void ShouldBeAbleToPassConcreteDictionaryToOptionalParameter() + { + var engine = new Engine(); + engine.SetValue("dictionaryTest", new DictionaryTest()); + engine.Evaluate("dictionaryTest.test4({ value: 'world' });"); + } + [Fact] public void ShouldSupportSpreadForDictionary() { diff --git a/Jint/Runtime/Interop/DefaultTypeConverter.cs b/Jint/Runtime/Interop/DefaultTypeConverter.cs index 576496d62f..a951366859 100644 --- a/Jint/Runtime/Interop/DefaultTypeConverter.cs +++ b/Jint/Runtime/Interop/DefaultTypeConverter.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; @@ -11,6 +12,7 @@ using Expression = System.Linq.Expressions.Expression; #pragma warning disable IL2026 +#pragma warning disable IL2062 #pragma warning disable IL2067 #pragma warning disable IL2070 #pragma warning disable IL2072 @@ -230,21 +232,49 @@ private bool TryConvert( var obj = Activator.CreateInstance(type, constructorParameters)!; - var members = type.GetMembers(); - foreach (var member in members) + // Check if the target type is also a string-keyed dictionary (e.g. Dictionary). + // In that case, populate the dictionary entries from the source rather than mapping to target members. + var targetTypeDescriptor = TypeDescriptor.Get(type); + if (targetTypeDescriptor.IsStringKeyedGenericDictionary && obj is IDictionary targetDict && typeDescriptor.KeysAccessor != null) { - // only use fields an properties - if (member.MemberType != MemberTypes.Property && - member.MemberType != MemberTypes.Field) + // Determine the value type expected by the target dictionary from its generic arguments. + var targetValueType = typeof(object); + if (type.IsGenericType) { - continue; + var genericArgs = type.GetGenericArguments(); + if (genericArgs.Length == 2) + { + targetValueType = genericArgs[1]; + } } - if (typeDescriptor.TryGetValue(value, member.Name, out var val) - || typeDescriptor.TryGetValue(value, member.Name.UpperToLowerCamelCase(), out val)) + var keys = (IEnumerable) typeDescriptor.KeysAccessor.GetValue(value)!; + foreach (var key in keys) { - var output = Convert(val, member.GetDefinedType(), formatProvider); - member.SetValue(obj, output); + if (typeDescriptor.TryGetValue(value, key, out var sourceVal)) + { + targetDict[key] = Convert(sourceVal, targetValueType, formatProvider); + } + } + } + else + { + var members = type.GetMembers(); + foreach (var member in members) + { + // only use fields and properties + if (member.MemberType != MemberTypes.Property && + member.MemberType != MemberTypes.Field) + { + continue; + } + + if (typeDescriptor.TryGetValue(value, member.Name, out var val) + || typeDescriptor.TryGetValue(value, member.Name.UpperToLowerCamelCase(), out val)) + { + var output = Convert(val, member.GetDefinedType(), formatProvider); + member.SetValue(obj, output); + } } }