Skip to content

Commit

Permalink
Fix #298
Browse files Browse the repository at this point in the history
  • Loading branch information
nilproject committed Dec 5, 2023
1 parent 0dc2e8f commit ea0aa5c
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 3 deletions.
27 changes: 25 additions & 2 deletions NiL.JS/Core/GlobalContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ public enum GlobalObjectsAssignMode
DenyThrowException
}

[Flags]
public enum MarshalinOptions
{
None = 0,
DictionaryAsObject = 1 << 0,
}

#if !NETCORE
[Serializable]
#endif
Expand All @@ -47,6 +54,7 @@ public sealed class GlobalContext : Context
public GlobalObjectsAssignMode GlobalObjectsAssignMode { get; set; }
public JsonSerializersRegistry JsonSerializersRegistry { get; set; }
public TimeZoneInfo CurrentTimeZone { get; set; }
public MarshalinOptions MarshalingOptions { get; set; }

public GlobalContext()
: this("")
Expand Down Expand Up @@ -314,10 +322,10 @@ public Function GetGenericTypeSelector(IList<Type> types)
return GetConstructor(type.MakeGenericType(parameters));
});
}

public JSValue WrapValue(object value)
{
if (value is null)
if (value is null)
return JSValue.Null;

return new ObjectWrapper(value, GetPrototype(value.GetType()));
Expand Down Expand Up @@ -507,6 +515,21 @@ public JSValue ProxyValue(object value)
_objectPrototype = GetPrototype(typeof(DictionaryWrapper<string, object>))
};
}
else if ((MarshalingOptions & MarshalinOptions.DictionaryAsObject) != 0
&& value is IEnumerable
&& (value is IDictionary || value.GetType().GetInterfaces().Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))))
{
var dictionaryInterface = value.GetType().GetInterfaces().FirstOrDefault(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>));
if (dictionaryInterface != null)
{
var types = dictionaryInterface.GetGenericArguments();
return DictionaryWrapper.Of(types[0], types[1], value);
}
else
{
return new DictionaryWrapper<object, object>(new UntypedDictionaryWrapper(value as IDictionary));
}
}
else if (value is Task)
{
Task<JSValue> result;
Expand Down
73 changes: 72 additions & 1 deletion NiL.JS/Core/Interop/DictionaryWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,86 @@
using System.Collections.Generic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using NiL.JS.Extensions;

namespace NiL.JS.Core.Interop
{
public static class DictionaryWrapper
{
internal static readonly MethodInfo OfMethod = typeof(DictionaryWrapper)
.GetMethod(nameof(Of));

internal static JSObject Of(Type keyType, Type valueType, object value)
{
return (JSObject)OfMethod
.MakeGenericMethod(keyType, valueType)
.Invoke(null, new[] { value })!;
}

public static DictionaryWrapper<TKey, TValue> Of<TKey, TValue>(IDictionary<TKey, TValue> dictionary)
=> new DictionaryWrapper<TKey, TValue>(dictionary);
}

public sealed class UntypedDictionaryWrapper : IDictionary<object, object>
{
private readonly IDictionary _target;

public UntypedDictionaryWrapper(IDictionary target)
{
_target = target;
}

public object this[object key] { get => _target[key]; set => _target[key] = value; }

public ICollection<object> Keys => _target.Keys.OfType<object>().ToList();

public ICollection<object> Values => _target.Values.OfType<object>().ToList();

public int Count => _target.Count;

public bool IsReadOnly => _target.IsReadOnly;

public void Add(object key, object value) => _target.Add(key, value);

public void Add(KeyValuePair<object, object> item) => _target.Add(item.Key, item.Value);

public void Clear() => _target.Clear();

public bool Contains(KeyValuePair<object, object> item) => _target.Contains(item.Key) && _target[item.Key].Equals(item.Value);

public bool ContainsKey(object key) => _target.Contains(key);

public void CopyTo(KeyValuePair<object, object>[] array, int arrayIndex) => _target.CopyTo(array, arrayIndex);

public IEnumerator<KeyValuePair<object, object>> GetEnumerator()
{
for (var e = _target.GetEnumerator(); e.MoveNext();)
yield return new KeyValuePair<object, object>(e.Key, e.Value);
}

public bool Remove(object key)
{
var contains = _target.Contains(key);
if (contains)
_target.Remove(key);
return contains;
}

public bool Remove(KeyValuePair<object, object> item) => Contains(item) && Remove(item.Key);

public bool TryGetValue(object key, [MaybeNullWhen(false)] out object value)
{
var contains = _target.Contains(key);
value = _target[key];
return contains;
}

IEnumerator IEnumerable.GetEnumerator() => _target.GetEnumerator();
}

public sealed class DictionaryWrapper<TKey, TValue> : JSObject
{
private readonly IDictionary<TKey, TValue> _target;
Expand Down
58 changes: 58 additions & 0 deletions Tests/Core/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,63 @@ public void ImplementationOfClass()
Assert.AreEqual(1, value0);
Assert.AreEqual(2, value1);
}

[TestMethod]
public void DictionaryWrapperRaw()
{
var globalContext = new GlobalContext();
globalContext.MarshalingOptions |= MarshalinOptions.DictionaryAsObject;
globalContext.ActivateInCurrentThread();

try
{
var dict = new Dictionary<string, Dictionary<string, object>> { ["key0"] = new() { ["key1"] = 123 } };

var jsobj = globalContext.ProxyValue(dict);

var test = jsobj["key0"]["key1"];

Assert.AreEqual(123, test.As<int>());
}
finally
{
globalContext.Deactivate();
}
}

[TestMethod]
public void DictionaryWrapperFromCode()
{
var globalContext = new GlobalContext();
globalContext.MarshalingOptions |= MarshalinOptions.DictionaryAsObject;

var dict = new Dictionary<string, Dictionary<string, object>> { ["key0"] = new() { ["key1"] = 123 } };

var context = new Context(globalContext)
{
{ "dict", globalContext.ProxyValue(dict) }
};

var test = context.Eval("dict.key0.key1");

Assert.AreEqual(123, test.As<int>());
}

[TestMethod]
public void DictionaryWrapperDisabled()
{
var globalContext = new GlobalContext();

var dict = new Dictionary<string, Dictionary<string, object>> { ["key0"] = new() { ["key1"] = 123 } };

var context = new Context(globalContext)
{
{ "dict", globalContext.ProxyValue(dict) }
};

var test = context.Eval("dict.key0");

Assert.IsTrue(!test.Exists);
}
}
}

0 comments on commit ea0aa5c

Please sign in to comment.