microsoft / Chakra-Samples

Repository for Chakra JavaScript engine related samples.
MIT License
216 stars 84 forks source link

JSON.stringify() and toString() #14

Closed SrinivasGourarum closed 8 years ago

SrinivasGourarum commented 8 years ago
  1. When using toString() on a host object, I am getting the value "[object [object Object]]" instead of [object Button]. (where Button is my host object). How to achieve this?
  2. When calling JSON.stringify(obj), I am getting the value as "{}" without any of the values of the properties that have been defined on this host object. Only the properties that have been set with values on the object are showing. Properties which have been set using "defineProperty" are not showing up. How can we get all the values of the defined properties in the JSON string.
liminzhu commented 8 years ago
  1. Per JavaScript language spec, you'll get "[object Object]" instead of "[object Button]" by default, assuming host is an object. If you want to get [object Button], the easiest way I could imagine is to overwrite the toString method in prototype. I'm very curious how you got "[object [object Object]]" as output and there might be a bug in our codebase. Would you mind sharing how you created/set up the host object?
  2. JSON.stringify ignores un-enumerable properties and properties whose value is undefined. If you defineProperty without specifying the descriptor, the default is that the property is un-enumerable and the property also does not have a value. Therefore the property would not show up in the output of stringify. One more thing to know is that stringify also ignores functions. To get properties to show up, you may want to use setProperty and actually give the properties some value.
SrinivasGourarum commented 8 years ago

1) I have tried to recreate the scenario using the sample application available at https://github.com/Microsoft/Chakra-Samples/tree/master/JavaScript%20Runtime%20Samples/JSRT%20Universal%20Windows%20Apps%20Hosting%20Samples/C%23

I have uploaded the same @ https://onedrive.live.com/redir?resid=9C4512A9CBBBEB98!108&authkey=!ANEQxP_vJjw2gSg&ithint=file%2czip

Execute the below code and observe the output. var btn = Button(); btn.toString();

2) I have defined the properties by specifying enumerable as true but have not specified any value. I was expecting the value to be obtained by invoking the getter callback which is defined in the descriptor. So are we saying that the value will not be enumerated unless we provide a value to the "value" key in the descriptor?

liminzhu commented 8 years ago

1) Thanks for taking the time to repro the output! It does seem that we had a bug in Windows 10 RTM build. We've already fixed it and I double-checked if you are on the latest Windows 10 Insider Preview build, it would output [object Object].

2) Your expectation was correct. If you have set enumerable to true and the getter callback in the descriptor, and the getter is not returning anything stringify would ignore (e.g. undefined, function), then it should show up in the stringify output. That case you don't have to specify the value in descriptor.

liminzhu commented 8 years ago

Could you share the object that you're trying to stringify? It would be easier for me to tell what's going on :smiley:.

Feel free to share here or send it to me (limzh@microsoft.com).

SrinivasGourarum commented 8 years ago
  1. Thanks for the update :smiley:.
  2. After some analysis, I found that JSON.stringify is populating all enumerable properties and those that have defined a getter callback defined. However, it is not enumerating the properties that have been defined in its prototype object. I have uploaded the sample @ . https://onedrive.live.com/redir?resid=9C4512A9CBBBEB98!109&authkey=!APdFm2o6Pn2Fc0M&ithint=file%2czip

Execute the below code and observe the output. var btn = new Button(); JSON.stringify(btn);

We have one property (named "ownProperty")defined on the Button object and one property (named "propertyinPrototype" on its prototype. It is not enumerating the property that is defined on the prototype object.

Please let me know if more information is needed.

liminzhu commented 8 years ago

Thanks for sharing :D. JSON.stringify actually only stringifies own properties so the prototype properties won't show up. You can look it up in ES6 spec or I think this stack overflow post did a pretty good job explaining the gist of it.

SrinivasGourarum commented 8 years ago

Thanks for the clarification. I could have defined all the properties on the object itself instead of its prototype, in which case stringify would have enumerated all the properties. I thought doing so might impact the performance and hence used the prototype which once created could be used for all instances of that particular object.

SrinivasGourarum commented 8 years ago

Is there a way that I can override JSON.stringify? Since I have defined all the properties in the prototype, if it is possible to override, I could call the method on the object's prototype and get the expected result.

There is significant performance improvement by creating the prototype once and then reusing it for all objects of that particular type. So I can not every time define the properties on all the objects.

Do you see any thing else breaking apart from "JSON.stringify" with the approach that I am using?

liminzhu commented 8 years ago

You sure can override just by writing a new function and assigning it to JSON.stringify. It depends but it might be better to just use the new function instead of overriding built-ins for that purpose.

There are side effects of your current approach, for instance, the data originally in prototype would go from static to per-instance once you stringify and parse, which you may be fine with. It's really hard for me to tell what is right without knowing exactly what you are trying to do. There are alternatives, for example you can think from the angle of how to parse and reconstruct the object instead of how to stringify it. You can have a Button constructor that re-constructs an object from a string and you don't have to worry about the stuff in prototype. This won't work well if you are dealing with lots of different classes. It's just food for thought and might not work well in your case.

SrinivasGourarum commented 8 years ago

I have been able to override the method in a js file. Just so that I do not have implement all the functionality, I have created a cloned object and set properties on it using the properties of original object's prototype. It goes some thing like this:

var nativeImplementation = JSON.stringify; JSON.stringify = function (obj) { //create a clone object and call the native method var cloneObj = JSON.parse(nativeImplementation(obj)); updateClone(obj, cloneObj); return nativeImplementation(cloneObj); }

function updateClone(obj, cloneObj) { //copy the properties from the obj's prototype to the clone obj for (var key in obj) { cloneObj[key] = obj[key];//obj's own properties are already on cloneObj, but nevertheless. } }

Thank you for the valuable input. I believe that if there was a concept of a class in Chakra, which could be created once (by defining properties/methods on it) and all objects of that class type could be created using this class, it would definitely help with the performance. Please consider it. I would be more than happy to provide more information in this regard.

You may close this issue with closing comments if any.

Thanks, Srinivas.

liminzhu commented 8 years ago

Thanks for the input Srinivas! I'm glad you find a way to work things out.

Just to clarify, are you talking about creating class natively or in JS?

SrinivasGourarum commented 8 years ago

I am taking about creating it natively. I am not taking about ES6 Classes in javascript.

We have this api JsCreateObject which would create an object. I was talking about being able to create this object using a class (which has already the properties/methods defined).

liminzhu commented 8 years ago

That's what I thought :D. Note taken.