Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Javascript objects defined in .NET don't Stringify correctly #37

Closed
DAVco4 opened this issue Jun 17, 2016 · 4 comments
Closed

Javascript objects defined in .NET don't Stringify correctly #37

DAVco4 opened this issue Jun 17, 2016 · 4 comments
Labels

Comments

@DAVco4
Copy link

DAVco4 commented Jun 17, 2016

If I define a Javascript object in C# code using the pattern shown here: Building an instance class, and I call JSON.stringify on an instance of that object, none of the expected fields are shown in the output. However, I am still able to access those fields in the same Javascript code (i.e I can use their values, etc). If I set an 'invisible' field from Javascript code and then perform the stringify, it appears in the output as I would expect.

C# class

using Jurassic;
using Jurassic.Library;

public class MyJsObjectConstructor : ClrFunction
{
    public MyJsObjectConstructor(ScriptEngine js)
        : base(js.Function.InstancePrototype, "MyJsObject", new MyJsObjectInstance(js.Object.InstancePrototype))
    {
    }

    [JSConstructorFunction]
    public MyJsObjectInstance Construct(int i)
    {
        return new MyJsObjectInstance(this.InstancePrototype, i);
    }
}


public class MyJsObjectInstance : ObjectInstance
{
    public MyJsObjectInstance(ObjectInstance prototype)
        : base(prototype)
    {
        this.PopulateFields();
        this.PopulateFunctions();
    }

    public MyJsObjectInstance(ObjectInstance prototype, int i)
        : base(prototype)
    {
    }

    [JSField]
    public string myString = "This is my string";
}

Javascript
var myJsObject = new MyJsObject(); console.log(JSON.stringify(myJsObject)); myJsObject.myString = "Updated the string"; console.log(JSON.stringify(myJsObject));

Javascript console output
This is my string {} {"myString":"Updated the string"}

@paulbartrum
Copy link
Owner

Don't use [JsField] -- it is not generally useful. Instead, you should use one of two approaches:

Simple property method

public class MyJsObjectInstance : ObjectInstance
{
    public MyJsObjectInstance(ObjectInstance prototype) : base(prototype)
    {
        // No need to call PopulateFunctions().
        MyString = "This is my string";
    }

    public string MyString
    {
        get { return this["myString"]; }
        set { this["myString"] = value; }
    }
}

Pros:

  • Reading and writing myString from Javascript is relatively fast.

Cons:

  • Setting the property in Javascript does not call the MyString setter.
  • Reading and writing MyString from .NET is slow.

Accessor property method

public class MyJsObjectInstance : ObjectInstance
{
    private string myString;

    public MyJsObjectInstance(ObjectInstance prototype) : base(prototype)
    {
        this.PopulateFunctions();
        this.myString = "This is my string";
    }

    [JsProperty(Name = "myString", IsEnumerable = true)]
    public string MyString
    {
        get { return this.myString; }
        set { this.myString = value; }
    }
}

Pros:

  • Reading and writing MyString from .NET is fast.
  • Setting the property in JavaScript does call the MyString setter.

Cons:

  • Reading and writing myString from JavaScript is slow.

@paulbartrum
Copy link
Owner

By the way, the behaviour with JSON.stringify is not a bug. JSON.stringify simply does not serialize properties which are marked as non-enumerable. If you want a property to appear in the serialized output, it needs to be enumerable.

@DAVco4
Copy link
Author

DAVco4 commented Jun 18, 2016

Hi Paul, thanks for the response.

I'm afraid that I was only able to get the first method working (simple property method) and showing up in the output of JSON.stringify - the second method does not seem to work despite the IsEnumerable parameter being set.

Performing propertyIsEnumerable() on an instance of the object returns true for the first method, but false for the second - so I think the IsEnumerable flag is being ignored for some reason.

public class MyJsObjectInstance : ObjectInstance
{

    private string mySecondString;

    public MyJsObjectInstance(ObjectInstance prototype)
        : base(prototype)
    {
        this.PopulateFunctions();
        this.PopulateFields();
    }

    public MyJsObjectInstance(ObjectInstance prototype, int i)
        : base(prototype)
    {
        this.MyFirstString = "my first string";
        this.MySecondString = "my second string";
    }

    // Works fine
    public string MyFirstString
    {
        get { return (string)this["myFirstString"]; }
        set { this["myFirstString"] = value; }
    }

    // Does not show up in JSON.stringify()
    [JSProperty(Name = "mySecondString", IsEnumerable = true)]
    public string MySecondString
    {
        get { return this.mySecondString; }
        set { this.mySecondString = value; }
    }
}

@paulbartrum
Copy link
Owner

Oh, that's because JSON.stringify doesn't serialize properties in the prototype. mySecondString is in the prototype but myFirstString is not; it's set directly on the object.

Try this:

console.log(JSON.stringify(myJsObject));  // doesn't show mySecondString
console.log(JSON.stringify(Object.getPrototypeOf(myJsObject)));  // does show mySecondString

I checked, and according to the spec this behaviour (not serializing properties in the prototype) is correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants