convert returned values of CallGlobalFunction to anonimous types

Sep 25, 2011 at 3:44 PM

Is there a way to convert Objects returned from CallGlobalFunction  to anonimous type ?

like this ?

string script = "function foo () { var x = { Name: "Gandi" }; return x; }";

var engine = new Jurassic.ScriptEngine();                                       

engine.Evaluate(script);

var anonimoustype = engine.CallGlobalFunction ("foo");

var anonimoustype_net = new { Name = "Gandi" };

assert ( anonimoustype.Name == anonimoustype_net.Name);  // True

Sep 25, 2011 at 6:34 PM

You mean dynamic, not anonymous. And yes, this can be done, but you need to write the functionality yourself.

What I found is that when trying to get whole objects to and from jurassic/.NET I like to convert them to and from JSON which tends to get me all the data I want, so what you can do, is something like this:

var engine = new Jurassic.ScriptEngine();
var result = engine.Evaluate("(function() { var x = { Name: 'Gandi' }; return x; })()");
var json = JSONObject.Stringify(engine, result);
dynamic dyn = JObject.Parse(json);
Assert.AreEqual(dyn.Name, "Gandi");

The code here uses the Newtonsoft.Json json-provider. I haven't tested this, but I think it should work.

Sep 25, 2011 at 6:38 PM

On a second note, you probably meant anonymous type, not dynamic, and the answer to that question is the same, yes it can be done, but you won't get compile-time support for it like you do with dynaimcs (in other words, it won't compile if you write myObj.Name, you need to use reflection to get the value (which is a lot of extra work)). Creating a anonymous type at run-time is possible, however, to do so you need to learn emit and stuff like that (not pretty). What you want is to be able to write myJsObject.SomeProperty and get that property, and with dynamics you can, with anonymous types, you can't.

Coordinator
Sep 25, 2011 at 9:27 PM

Implementing basic IDynamicMetaObjectProvider support for ObjectInstance should be pretty easy and would enable this scenario.  I'll put it on my TODO list :-)

Coordinator
Sep 25, 2011 at 10:22 PM
Edited Sep 25, 2011 at 10:23 PM

Here's a wrapper class I just wrote that seems to do the job:

using Jurassic;
using Jurassic.Library;


public class DynamicMetaObjectWrapper : System.Dynamic.DynamicObject
{
    private ObjectInstance obj;

    public DynamicMetaObjectWrapper(ObjectInstance obj)
    {
        this.obj = obj;
    }

    /// <summary>
    /// Provides the implementation for operations that get member values.
    /// </summary>
    /// <param name="binder"> Provides information about the object that called the dynamic
    /// operation. </param>
    /// <param name="result"> The result of the get operation. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
    {
        result = this.obj[binder.Name];
        if (TypeUtilities.IsUndefined(result))
            result = Undefined.Value;
        return true;
    }

    /// <summary>
    /// Provides the implementation for operations that set member values.
    /// </summary>
    /// <param name="binder"> Provides information about the object that called the dynamic
    /// operation. </param>
    /// <param name="value"> The value to set to the member. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TrySetMember(System.Dynamic.SetMemberBinder binder, object value)
    {
        this.obj[binder.Name] = value;
        return true;
    }

    /// <summary>
    /// Provides the implementation for operations that get a value by index.
    /// </summary>
    /// <param name="binder"> Provides information about the object that called the dynamic
    /// operation. </param>
    /// <param name="indexes"> The indexes that are used in the operation. </param>
    /// <param name="result"> The result of the index operation. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TryGetIndex(System.Dynamic.GetIndexBinder binder, object[] indexes, out object result)
    {
        if (indexes.Length != 1)
            throw new NotImplementedException();
        result = this.obj[indexes[0].ToString()];
        if (TypeUtilities.IsUndefined(result))
            result = Undefined.Value;
        return true;
    }

    /// <summary>
    /// Provides the implementation for operations that set a value by index.
    /// </summary>
    /// <param name="binder"> Provides information about the object that called the dynamic
    /// operation. </param>
    /// <param name="indexes"> The indexes that are used in the operation. </param>
    /// <param name="value"> The value to set to the object that has the specified index. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TrySetIndex(System.Dynamic.SetIndexBinder binder, object[] indexes, object value)
    {
        if (indexes.Length != 1)
            throw new NotImplementedException();
        this.obj[indexes[0].ToString()] = value;
        return true;
    }

    /// <summary>
    /// Provides the implementation for operations that invoke a member.
    /// </summary>
    /// <param name="binder"> Provides information about the dynamic operation. </param>
    /// <param name="args"> The arguments that are passed to the object member during the
    /// invoke operation. </param>
    /// <param name="result"> The result of the member invocation. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result)
    {
        return this.obj.TryCallMemberFunction(out result, binder.Name, args);
    }
}
Sep 26, 2011 at 12:03 AM

This looks nice and all, but one thing you should change is probably that the dynamic object should return new dynamic objects.

Sep 26, 2011 at 6:05 PM

I try to compile, but i get the error :

  TypeUtilities.IsUndefined is not defined.

Also can you provide an example of using DynamicMetaObjectWrapper. Is it like the example bellow ?

var engine = new Jurassic.ScriptEngine();
var result = engine.Evaluate("(function() { var x = { Name: 'Gandi' }; return x; })()");
var json = JSONObject.Stringify(engine, result);

dynamic dyn = new DynamicMetaObjectWrapper (json);

Assert.AreEqual(dyn.Name, "Gandi");

thanks.


Coordinator
Sep 26, 2011 at 8:58 PM

Oops, TypeUtilities.IsUndefined is marked as internal, sorry.

Here's a new version of the class:

public class DynamicMetaObjectWrapper : System.Dynamic.DynamicObject
{
    private ObjectInstance obj;

    public DynamicMetaObjectWrapper(ObjectInstance obj)
    {
        this.obj = obj;
    }

    /// <summary>
    /// Creates a wrapper if the given value is an ObjectInstance, otherwise returns the value
    /// unaltered.
    /// </summary>
    /// <param name="value"> The value to wrap. </param>
    /// <returns> A DynamicMetaObjectWrapper instance, or the input value. </returns>
    public static object Wrap(object value)
    {
        if (value is ObjectInstance)
            return new DynamicMetaObjectWrapper((ObjectInstance)value);
        return value;
    }

    /// <summary>
    /// Provides the implementation for operations that get member values.
    /// </summary>
    /// <param name="binder"> Provides information about the object that called the dynamic
    /// operation. </param>
    /// <param name="result"> The result of the get operation. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
    {
        result = Wrap(this.obj[binder.Name]);
        return true;
    }

    /// <summary>
    /// Provides the implementation for operations that set member values.
    /// </summary>
    /// <param name="binder"> Provides information about the object that called the dynamic
    /// operation. </param>
    /// <param name="value"> The value to set to the member. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TrySetMember(System.Dynamic.SetMemberBinder binder, object value)
    {
        this.obj[binder.Name] = value;
        return true;
    }

    /// <summary>
    /// Provides the implementation for operations that get a value by index.
    /// </summary>
    /// <param name="binder"> Provides information about the object that called the dynamic
    /// operation. </param>
    /// <param name="indexes"> The indexes that are used in the operation. </param>
    /// <param name="result"> The result of the index operation. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TryGetIndex(System.Dynamic.GetIndexBinder binder, object[] indexes, out object result)
    {
        if (indexes.Length != 1)
            throw new NotImplementedException();
        result = Wrap(this.obj[indexes[0].ToString()]);
        return true;
    }

    /// <summary>
    /// Provides the implementation for operations that set a value by index.
    /// </summary>
    /// <param name="binder"> Provides information about the object that called the dynamic
    /// operation. </param>
    /// <param name="indexes"> The indexes that are used in the operation. </param>
    /// <param name="value"> The value to set to the object that has the specified index. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TrySetIndex(System.Dynamic.SetIndexBinder binder, object[] indexes, object value)
    {
        if (indexes.Length != 1)
            throw new NotImplementedException();
        this.obj[indexes[0].ToString()] = value;
        return true;
    }

    /// <summary>
    /// Provides the implementation for operations that invoke a member.
    /// </summary>
    /// <param name="binder"> Provides information about the dynamic operation. </param>
    /// <param name="args"> The arguments that are passed to the object member during the
    /// invoke operation. </param>
    /// <param name="result"> The result of the member invocation. </param>
    /// <returns> <c>true</c> if the operation is successful; otherwise, <c>false</c>. </returns>
    public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result)
    {
        return Wrap(this.obj.TryCallMemberFunction(out result, binder.Name, args));
    }
}

Here's an example of use:

var engine = new Jurassic.ScriptEngine();
dynamic result = DynamicMetaObjectWrapper.Wrap(engine.Evaluate("(function() { var x = { Name: 'Gandi' }; return x; })()"));
Assert.AreEqual(result.Name, "Gandi");