What does 'symbol-keyed' mean in `JSON.stringify` - javascript

There is a Object generated by Node.js, it looks like this when I use console.log:
{ dataValues: { a: 1, b: 2 }, fn1: function(){}, fn2: function(){} }
when I use JSON.stringify, it return this string:
{"a":1,"b":1}
I checked the mozilla developer center and found this:
All symbol-keyed properties will be completely ignored, even when using the replacer function.
I think the 'dataValues' must be the 'symbol-keyed' property, but what does 'symbol-keyed' mean?
btw, I use the sequelizejs ORM lib to generate this object.

I found the reason finally in the same page:
If an object being stringified has a property named toJSON whose value is a function, then the toJSON method customizes JSON stringification behavior: instead of the object being serialized, the value returned by the toJSON method when called will be serialized.
It runs on browser normally.
Here is the jsfiddle to run it like I asked.
Code:
function test(data) {
for(var key in data){
this[key] = data[key];
}
}
test.prototype.toJSON = function(){
return this.dataValues;
}
var a = new test({dataValues: {a:1, b:2}});
console.log(a);//the instance
console.log(JSON.stringify(a));//{"a":1,"b":1}

Nah, the relevant part to your issue is this blurb:
If undefined, a function, or a symbol is encountered during conversion
it is either omitted (when it is found in an object) or censored to
null (when it is found in an array).
So in other words, if your JSON object contains functions, they are omitted during the JSON.stringify process.
"symbol-keyed" refers to a new primitive type introduced in ecmascript6. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof for more info.
This is an example of a "symbol-keyed" property:
{[Symbol("foo")]: "foo"}
Reference for JavaScript Symbol: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol

Related

Destructured object loses reference after being modified

Object reference is lost after an assignment. Any previous references are no longer relevant.
I have the following:
// lib.js
const obj = { prop: { data: { some: 'empty' } } };
function loadObject() {
obj.prop.data = { some: 'load' };
}
modules.exports = { prop: obj.prop, data: obj.prop.data, loadObject() };
and
// main.js
const { prop, data, loadObject } = require('./lib');
loadObject();
console.log(prop.data); // data changed (new reference)
console.log(data); // data not changed (old reference)
How can I modify data without losing it's initial reference?
PS:
I have a bunch of files importing this lib and it's relying on that data.
I'd rather not replace it to prop.data or reread it again in each file that uses it.
You replaced data with a new object. There’s no way to change everything that references an old object to point to a new object instead, so if you want to keep the exact same API, you have to alter the object in place instead of replacing it – maybe like you did before:
// if it’s a single property that’s the same before and after
function loadObject() {
obj.prop.data.some = 'load';
}
// if the properties before are a subset of the properties after
function loadObject() {
Object.assign(obj.prop.data, { some: 'load' });
}
// if you need to change the set of properties entirely
function loadObject() {
for (const key of Object.keys(obj.prop.data)) {
delete obj.prop.data[key];
}
Object.assign(obj.prop.data, { some: 'load' });
}
The problem is not related to different modules systems or objects de-structuring. Please see the following example.
const obj = {prop: {data: {some: 'empty'}}};
const objRef = obj.prop.data;
console.log(objRef);
obj.prop.data.some = 'not empty';
console.log(objRef);
obj.prop.data = {other: 'hello!'};
console.log(objRef);
console.log(obj);
Remember that each time that you use the bracket notation to create objects {} you are implicitly instantiating a new "independent" object. This is still valid for nested objects like the one in your example. Indeed you have multiple nested references of multiple objects.
The 'name' of the nested object (the key of the parent) acts as a variable that contains the reference of the new object that you are instantiating using {}.
I know that this may appear confusing (because it really is), but if you follow the previous snippet I'm sure you will get it. objRef keeps a reference of the "independent" object {some: 'empty'}, that at the same time is stored as a reference in all the other objects until the primary object obj. In this line: obj.prop.data = {other: 'hello!'}; we are completely erasing {some: 'empty'} reference from the primary object, but it is kept in objRef which will be the only remaining reference for that object. We are separating objRef from obj.
Summary:
Each time you use {} -> equivalent to -> new Object()
obj.prop.data.some = 'not empty'; -> Changing property value. Will be seen in all references of data: {} object.
obj.prop.data = {other: 'hello!'}; -> Completely replacing data: {} object reference in main object (destroying reference) (still kept in objRef).
If it is not clear I could try explaining it in a different way. Please let me know.
Having any variable seemingly alter itself on its own (as a consumer of the library would experience) is quite unintuitive, and is often forbidden by linters for the sake of code clarity - if that sort of logic is needed, consider exporting a function which returns the current data instead, eg
modules.exports = {
prop: obj.prop,
getData: () => obj.prop.data,
loadObject
};
(note that to export the function, you need to just reference the function, not call it)
While it would be possible to export a mutable binding if you were using ES6 modules, that's not the case here, and wouldn't be a good idea anyway.

JSON.parse Reviver function: Access to object being revived?

I want to confirm the behavior I am seeing regarding the this reference, and the ability to modify keys (not just values) in an object using the reviver function.
If I pass my reviver function using function(key,value {...} as opposed to using an arrow function (key, value) => {...}, the this reference seems to refer to the object being revived. This is true for sub-objects in the JSON as well. I am seeing this in node.js 8.x on the server, and in Chrome current on the client.
Understandably, if I pass the function as an arrow function, the calling context is preserved.
I am relying on this to add and delete some keys as I parse the JSON.
Can I rely on this behavior?
var aTestStr = '{"prop1": "this is prop 1",'
+'"prop2": {"prop2A": 25, "prop2B": 13, "prop2C": "This is 2-c"}'
+'}';
var aTestObj = JSON.parse(aTestStr, function(key, value) {
//at this point, this refers to the object being revived
//E.g., when key == 'prop1', this is an object with prop1 and prop2
//when key == prop2B, this is an object with prop2A, prop2B and prop2C
//So is this code reliable?
if (key == this.prop2B) {
//Do something, add a prop to this:
this.prop2BDif = 100 - this.prop2B;
}
});
Yes it is documented: JSON.parse documentation in the MDN
If a reviver is specified, the value computed by parsing is transformed before being returned. Specifically, the computed value and all its properties (beginning with the most nested properties and proceeding to the original value itself) are individually run through the reviver. Then it is called, with the object containing the property being processed as this, and with the property name as a string, and the property value as arguments.

Why does JSON.stringify return empty object notation "{}" for an object that seems to have properties?

The following example shows that JSON.stringify() returns the string "{}" for SpeechSynthesisVoice objects:
var voiceObject = window.speechSynthesis.getVoices()[0];
JSON.stringify(voiceObject); //returns "{}"?
Complete example: JSFiddle
Why does it return "{}" and not something like "{voiceURI: "Google Deutsch", name: "Google Deutsch", lang: "de-DE", localService: false, default: false}"?
Note that the above example does not work for chrome or iOS; it is targeted for Mozilla Firefox.
JSON.stringify includes an object's own, enumerable properties (spec) that have values that aren't functions or undefined (as JSON doesn't have those), leaving out ones it inherits from its prototype, any that are defined as non-enumerable, and any whose value is a function reference or undefined.
So clearly, the object you get back from getVoices()[0] has no own, enumerable properties that can be represented in JSON. All of their properties must be either inherited, defined as non-enumerable, or (though it's probably not the case here) functions or undefined.
You can fix this by doing:
var voiceObject = window.speechSynthesis.getVoices()[0];
var newvoiceObject = $.extend(newvoiceObject,voiceObject);
JSON.stringify(newvoiceObject); //returns correct JSON string
...but keep in mind the object type will change, if you require that the object is of a specific type.
The answer of T.J Crowder works for me, i was creating my object like this:
Object.defineProperties(completeObj, {
[attributeName]: {
value: finalValue
}
});
I changed for this and the problem was solved:
Object.defineProperties(completeObj, {
[attributeName]: {
value: finalValue,
enumerable: true
}
});

Why is JSON.stringify not serializing prototype values?

I have been working with a fair bit of JSON parsing and passing in Javascript within Node.js and browsers recently and bumped into this conundrum.
Any objects I created using a constructor, cannot be fully serialized fully via JSON.stringify, UNLESS I initialised all values within the constructor individually! This means my prototype becomes essentially useless in designing these classes.
Can someone shed some light on why the following doesn't serialize as I expect?
var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a);
What happens:
a_string == { "initialisedValue" : "You can see me!" }
I would expect:
a_string == { "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
Update (01-10-2019):
Finally noticed #ncardeli 's Answer, which does allow us to do something like the following to achieve my above requirement (in 2019!):
Replace
var a_string = JSON.stringify(a);
with
var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));
Full code:
var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));
console.log(a_string)
Simply because this is the way JSON works. From the ES5 spec:
Let K be an internal List of Strings consisting of the names of all the own properties of value whose [[Enumerable]] attribute is true.
This makes sense, because there is no mechanism in the JSON specification for preserving information that would be required to parse a JSON string back into a JavaScript object if inherited properties were included. In your example, how would this parsed:
{ "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
There is no information to parse it into anything other than a flat object with 2 key-value pairs.
And if you think about it, JSON is not intended to map directly to JavaScript objects. Other languages must be able to parse JSON strings into simple structures of name-value pairs. If JSON strings contained all the information necessary to serialize complete JavaScript scope chains, other languages may be less capable of parsing that into something useful. In the words of Douglas Crockford on json.org:
These [hash tables and arrays] are universal data structures. Virtually all modern programming languages support them in one form or another. It makes sense that a data format that is interchangeable with programming languages also be based on these structures.
I'd like to add that, even though JSON.stringify will only stringify the object's own properties, as explained in the accepted answer, you can alter the behavior of the stringification process by specifying an array of String as the second parameter of JSON.stringify (called a replacer array).
If you specify an array of String with the whitelist of properties to stringify, the stringification algorithm will change its behavior and it will consider properties in the prototype chain.
From ES5 spec:
If PropertyList is not undefined, then
a. Let K be PropertyList.
Else
a. Let K be an internal List of Strings consisting of the names of
all the own properties of value whose [[Enumerable]] attribute is
true. The ordering of the Strings should be the same as that used by
the Object.keys standard built-in function.
If you know the name of the properties of the object to stringify beforehand, you can do something like this:
var a_string = JSON.stringify(a, ["initialisedValue", "uninitialisedValue"]);
// a_string == { "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
There is another possibility to still have properties from the prototype to be stringified.
As of the JSON spec (15.12.3 stringify http://es5.github.io/#x15.12.3):
[...]
2. If Type(value) is Object, then
a. Let toJSON be the result of calling the [[Get]] internal method of value with argument "toJSON".
b. If IsCallable(toJSON) is true
i. Let value be the result of calling the [[Call]] internal method of toJSON passing value as the this value and with an argument list consisting of key.
[...]
So yu can write your own JSON stringifier function. A general implementation could be:
class Cat {
constructor(age) {
this.age = age;
}
get callSound() {
// This does not work as this getter-property is not enumerable
return "Meow";
}
toJSON() {
const jsonObj = {}
const self = this; // If you can use arrow functions just use 'this'-keyword.
// Object.keys will list all 'enumerable' properties
// First we look at all own properties of 'this'
Object.keys(this).forEach(function(k) {
jsonObj[k] = self[k];
});
// Then we look at all own properties of this's 'prototype'
Object.keys(Object.getPrototypeOf(this)).forEach(function(k) {
jsonObj[k] = self[k];
});
return JSON.stringify(jsonObj);
}
}
Object.defineProperty(Cat.prototype, 'callSound2', {
// The 'enumerable: true' is important!
value: "MeowMeow", enumerable: true
});
let aCat = new Cat(4);
console.log(JSON.stringify(aCat));
// prints "{\"age\":4,\"callSound2\":\"MeowMeow\"}"
This works as long as the right properties (those you actually want to be JSON stringified) are enumerable and those you dont want to be stringified aren't. So you need to be careful which properties you make enumerable or become implicitly enumerable when you assign values to 'this'.
Another possibility is to assign each property you actually want to be stringifed manually one by one. Which might be less error prone.

"too much recursion" error when calling JSON.stringify on a large object with circular dependencies

I have an object that contains circular references, and I would like to look at the JSON representation of it. For example, if I build this object:
var myObject = {member:{}};
myObject.member.child = {};
myObject.member.child.parent = myObject.member;
and try to call
JSON.stringify(myObject);
I get the error "too much recursion", not surprisingly. The "child" object has a reference to its "parent" and the parent has a reference to its child. The JSON representation does not have to be perfectly accurate, as I'm only using it for debugging, not for sending data to a server or serializing the object into a file or anything like that. Is there a way to tell JSON.stringify to just ignore certain properties (in this case the parent property of the child object), so that I would get:
{
"member" : {
"child" : {}
}
}
The closest I can think of is to use getChild() and getParent() methods instead of just members, because JSON.stringify ignores any properties that are functions, but I'd rather not do that if I don't have to.
You can pass a function as the second argument to stringify.
This function receives as arguments the key and value of the member to stringify.
If this function returns undefined, the member will be ignored.
alert(JSON.stringify(myObject, function(k, v) {
return (k === 'member') ? undefined : v;
}));
...or use e.g. firebug or use the toSource()-method, if you only want to see whats inside the object.
alert(myObject.toSource());
From the crockford implementation (which follows the ECMA specification):
If the stringify method sees an object that contains a toJSON method, it calls that method, and stringifies the value returned. This allows an object to determine its own JSON representation.
Then something like this should work just fine:
var myObject =
{
member: { child: {} }
}
myObject.member.child.parent = myObject.member;
myObject.member.child.toJSON = function ()
{
return 'no more recursion for you.';
};
console.log(JSON.stringify(myObject));​
http://jsfiddle.net/feUtk/

Categories