4

I have a .NET Generic Dictionary<> that I want to pass to a JavaScript function which I am running in Jint.

Jint doesn't treat the .NET Dictionary like a JavaScript object which can be treated like a Dictionary. You can access the .NET properties and methods on the object but not the Extension methods.

So while I can get a count of the Dictionary Keys I cannot enumerate it or call ToArray() on it.

I can used dict[key] to read values out of the Dictionary but in this instance I do not know the keys in advance.

How can I enumerate the keys or get all entries in the .NET Generic Dictionary?

I'm open to doing something to the dictionary or converting it before I pass it it in or figuring out how to do it in the JavaScript. I would prefer not to have to pass an array of the keys separately. This is inside another data structure and doing that for each dictionary would make it more complicated but it is one of the options that I'm considering if I can't find another solution.

I would prefer to stay away from using dynamics. I've had trouble with them leaking memory when used heavily in the past.

Rob
  • 26,989
  • 16
  • 82
  • 98
Zack
  • 2,291
  • 2
  • 23
  • 38
  • I've never used [tag:jint] before, but it looks like your question is how to enumerate members of an object in javascript. Objects in javascript *are* dictionaries, and are typically serialized as so. [This](https://stackoverflow.com/questions/684672/how-do-i-loop-through-or-enumerate-a-javascript-object) will likely answer your question – Rob Sep 08 '17 at 06:40
  • You can iterate over javascript object properties like [this](https://stackoverflow.com/questions/85992/how-do-i-enumerate-the-properties-of-a-javascript-object), however, it is hard to tell exactly what you are wanting to do here. On the .net side, you can enumerate a `Dictionary` KeyCollection, and get a list of all keys. – Daniel Park Sep 08 '17 at 06:42
  • doesn't work in jint. it the object was declared in JavaScript I could use the normal js way of doing it, but its a .NET object passed into a js environment and you can't treat it like a regular js object. I already specifically tried for(var i in mydict) and for(var i in mydict.Keys) neither of which worked. – Zack Sep 08 '17 at 06:47
  • Jint does not try to resolve extension methods, like `ToArray`. You can try to roll out your own version of `ObjectWrapper` that does , but I just tried and it's not trivial (some useful methods are private so you have to copy them) – Titian Cernicova-Dragomir Sep 08 '17 at 06:58

3 Answers3

5

I just ran into the same problem and solved it by using the enumerator on the dictionary:

// Javascript in Jint:
var enumerator = myDictionary.GetEnumerator();
while (enumerator.MoveNext()) {
    var key = enumerator.Current.Key;
    var value = enumerator.Current.Value;
    // do stuff with key and/or value
}

The same way you could just iterate myDictionary.Keys or myDictionary.Values if you are not interested in both.

Note: You mentioned you could use dict[key]. At least for me where I had a complex struct as key, this didn't work. Apparently Jint can't handle generic indexers well and throws: System.InvalidOperationException: No matching indexer found.

djk
  • 943
  • 2
  • 9
  • 27
2

An simple alternative that might fit your particular situation is to pass the value as JSON instead. There are many cases when this is useful, particularly when writing tests using jint. This adds a dependency on a serializer though, and is probably slower, but very convenient and certainly predictable and debuggable.

using Jint;
using Newtonsoft.Json;

namespace Extensions
{
    public static class EngineExtentions {
        public static void SetJsonValue(this Engine source, string name, object value)
        {
            source.SetValue(name, JsonConvert.SerializeObject(value));
            source.Execute($"{name} = JSON.parse({name})");
        }
    }
}
Tewr
  • 3,713
  • 1
  • 29
  • 43
0

You could create your own ObjectWrapper that resolves the ToArray method for it's target object. Here's my quick stab at it, you will need to improve things like error handling, but it's a start:

public class ObjectWrapperExt: ObjectInstance, IObjectWrapper
{
    private ObjectWrapper _target;

    public ObjectWrapperExt(Engine engine, Object obj)
        : base(engine)
    {
        _target = new ObjectWrapper(engine, obj);
    }
    public object Target => _target.Target;

    public override PropertyDescriptor GetOwnProperty(string propertyName)
    {

        if (propertyName == "ToArray")
        {
            if (this.Properties.TryGetValue(propertyName, out var prop)) return prop;

            var descriptor = new PropertyDescriptor(new ToArrayFunctionInstance(Engine), false, true, false);
            Properties.Add(propertyName, descriptor);
            return descriptor;
        }

        return _target.GetOwnProperty(propertyName);
    }

    public override void Put(string propertyName, JsValue value, bool throwOnError)
    {
        _target.Put(propertyName, value, throwOnError);
    }

    public class ToArrayFunctionInstance : FunctionInstance
    {
        private static MethodInfo toArray = typeof(Enumerable).GetMethod("ToArray", BindingFlags.Static | BindingFlags.Public);

        public ToArrayFunctionInstance(Engine engine) : base(engine, null,null, false)
        {   
        }

        public override JsValue Call(JsValue thisObject, JsValue[] arguments)
        {
            var target = (thisObject.AsObject() is IObjectWrapper w)? w.Target : throw new NotSupportedException();
            var targetType = target.GetType();

            var enumImpl = targetType.GetInterfaces()
                .Where(_ => _.IsGenericType)
                .Where(_ => _.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                .FirstOrDefault();

            if(enumImpl != null)
            {
                var arg = enumImpl.GetGenericArguments();
                var value = toArray.MakeGenericMethod(arg).Invoke(null, new[] { target });
                return JsValue.FromObject(Engine, value);
            }
            throw new NotSupportedException();
        }
    }
}
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357