Skip to content

Commit

Permalink
Generic string-keyed dictionary indexing under interop
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma committed Nov 5, 2021
1 parent 6f4ad50 commit bbe3d1c
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 10 deletions.
23 changes: 23 additions & 0 deletions Jint.Tests/Runtime/InteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2898,5 +2898,28 @@ public void ShouldBeAbleToHandleInvalidClrConversionViaCatchClrExceptions()
var ex = Assert.Throws<JavaScriptException>(() => engine.Execute("a.age = \"It won't work, but it's normal\""));
Assert.Equal("Input string was not in a correct format.", ex.Message);
}

[Fact]
public void ShouldBeAbleToIndexJObjectWithStrings()
{
var engine = new Engine();

const string json = @"
{
'Properties': {
'expirationDate': {
'Value': '2021-10-09T00:00:00Z'
}
}
}";

var obj = JObject.Parse(json);
engine.SetValue("o", obj);
var value = engine.Evaluate("o.Properties.expirationDate.Value");
var wrapper = Assert.IsAssignableFrom<ObjectWrapper>(value);
var token = wrapper.Target as JToken;
var localDateTimeString = DateTime.Parse("2021-10-09T00:00:00Z").ToUniversalTime();
Assert.Equal(localDateTimeString.ToString(), token.ToString());
}
}
}
4 changes: 2 additions & 2 deletions Jint/Runtime/Interop/ObjectWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ public override JsValue Get(JsValue property, JsValue receiver)
var member = stringKey.ToString();

// expando object for instance
if (Target is IDictionary<string, object> stringKeyedDictionary)
if (_typeDescriptor.IsStringKeyedGenericDictionary)
{
if (stringKeyedDictionary.TryGetValue(member, out var value))
if (_typeDescriptor.TryGetValue(Target, member, out var value))
{
return FromObject(_engine, value);
}
Expand Down
50 changes: 42 additions & 8 deletions Jint/Runtime/Interop/TypeDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,30 @@ namespace Jint.Runtime.Interop
internal sealed class TypeDescriptor
{
private static readonly ConcurrentDictionary<Type, TypeDescriptor> _cache = new();
private static readonly Type _genericDictionaryType = typeof(IDictionary<,>);
private static readonly Type _stringType = typeof(string);

private readonly PropertyInfo _stringIndexer;

private TypeDescriptor(Type type)
{
IsArrayLike = DetermineIfObjectIsArrayLikeClrCollection(type);
IsDictionary = typeof(IDictionary).IsAssignableFrom(type) || typeof(IDictionary<string, object>).IsAssignableFrom(type);
// check if object has any generic dictionary signature that accepts string as key
foreach (var i in type.GetInterfaces())
{
if (i.IsGenericType
&& i.GetGenericTypeDefinition() == _genericDictionaryType
&& i.GenericTypeArguments[0] == _stringType)
{
_stringIndexer = i.GetProperty("Item");
break;
}
}

IsDictionary = _stringIndexer is not null || typeof(IDictionary).IsAssignableFrom(type);

// dictionaries are considered normal-object-like
IsArrayLike = !IsDictionary && DetermineIfObjectIsArrayLikeClrCollection(type);

IsEnumerable = typeof(IEnumerable).IsAssignableFrom(type);

if (IsArrayLike)
Expand All @@ -26,6 +45,7 @@ private TypeDescriptor(Type type)
public bool IsArrayLike { get; }
public bool IsIntegerIndexedArray { get; }
public bool IsDictionary { get; }
public bool IsStringKeyedGenericDictionary => _stringIndexer is not null;
public bool IsEnumerable { get; }
public PropertyInfo LengthProperty { get; }

Expand All @@ -38,12 +58,6 @@ public static TypeDescriptor Get(Type type)

private static bool DetermineIfObjectIsArrayLikeClrCollection(Type type)
{
if (typeof(IDictionary).IsAssignableFrom(type) || typeof(IDictionary<string, object>).IsAssignableFrom(type))
{
// dictionaries are considered normal-object-like
return false;
}

if (typeof(ICollection).IsAssignableFrom(type))
{
return true;
Expand All @@ -65,5 +79,25 @@ private static bool DetermineIfObjectIsArrayLikeClrCollection(Type type)

return false;
}

public bool TryGetValue(object target, string member, out object o)
{
if (!IsStringKeyedGenericDictionary)
{
ExceptionHelper.ThrowInvalidOperationException("Not a string-keyed dictionary");
}

// we could throw when indexing with an invalid key
try
{
o = _stringIndexer.GetValue(target, new [] { member });
return true;
}
catch (TargetInvocationException tie) when (tie.InnerException is KeyNotFoundException)
{
o = null;
return false;
}
}
}
}

0 comments on commit bbe3d1c

Please sign in to comment.