This question already has answers here:
Why is JSON.stringify not serializing prototype values?
(3 answers)
Closed 8 years ago.
I have one prototype of this structure:
function MyObj() { }
MyObj.prototype = {
prop1: {
prop11: null,
prop12: null,
prop13: null,
},
prop2: {
prop21: null,
prop22: null,
prop23: null,
},
prop3: {
prop31: [],
prop32: '',
prop34: [],
},
prop4: {
prop41: null,
},
}
When I call JSON.stringify(myObjInstance), I get {}, why?
This happens because prop1 through prop4 are properties of the prototype and not of the instantiated object.
You can compare it to something like:
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
// add property to bag
}
}
Only the properties of the object itself are used.
Because JSON.stringify only includes an object's own properties (specifically, own enumerable properties), not properties an object inherits from its prototypes or any of its own properties that are non-enumerable.
So for example: Live Copy | Live Source
function Foo() { }
Foo.prototype.inherited = true;
var f = new Foo();
Object.defineProperty(f, "ownNonEnumerable", {
value: true
});
f.ownEnumerable = true;
console.log(f.inherited); // true
console.log(f.ownNonEnumerable); // true
console.log(f.ownEnumerable); // true
console.log(JSON.stringify(f)); // {"ownEnumerable": true}
JSON.stringify will only include a property if it can be found with hasOwnProperty, and
new MyObj().hasOwnProperty("prop1") === false;
Try this instead:
JSON.stringify(MyObj.prototype);
JSON.stringify(obj) will output the properties that the object itself has, not its prototype. Its prototype is a different object.
What you're looking for would be something like this
JSON.stringify(MyObj.prototype)
I had the same issue, when I start working with JSON.stringify. In order to create a string the object you create needs to of typeof == object. Otherwise it won't work.
Try to alert the typeOf of MyObj. If it not Object that won't work.
MyObj = {
prop1: {
prop11: null,
prop12: null,
prop13: null,
},
prop2: {
prop21: null,
prop22: null,
prop23: null,
},
prop3: {
prop31: [],
prop32: '',
prop34: [],
},
prop4: {
prop41: null,
},
}
Related
const a = {x:1};
console.log({...a}.x);
// 1
console.log(window.Math);
// Math {abs: ƒ, acos: ƒ, acosh: ƒ, asin: ƒ, asinh: ƒ, …}
console.log({...window}.Math);
// undefined
I don't understand why {...a}.x evaluates 1, but {...window}.Math evaluates undefined.
That's because Math is not enumerable.
The ECMA-2018 (ES9) specs is a little hard to read. MDN and a proposal page stated: {...obj} creates a new object for all of obj's (1) own and (2) enumerable properties. Math is window's own property, but not enumerable:
console.log(window.hasOwnProperty("Math"));
console.log(Object.getOwnPropertyDescriptor(window, "Math"));
You can reproduce the situation with an object:
obj = {};
Object.defineProperty(obj, 'x', {
enumerable: false,
value: 123
});
console.log(obj); // Google Chrome prints { x: 123 } but StackOverflow prints {} and Node.js prints {} too
console.log(obj.x);
console.log(({...obj}).x);
{...Window} is using the spread operator to make a copy into a new object. Math is not enumerable so it won't be copied to the new object.
You can test it by your self:
const a = {}
Object.defineProperty(a, "prop1", { value: "enumerable", enumerable: true })
Object.defineProperty(a, "prop2", { value: "not enumerable", enumerable: false })
Then copy your object:
{...a}.prop1 //"enumerable"
{...a}.prop2 // undefined
I have a JSON object(mainObj) which in turn has objects (say obj1, obj2, obj3). What I am trying to achieve is when I check for a condition iterating through every obj in the mainObj and if it holds true, I want to add only the name of that obj in an array of String. Something like,
for(obj in mainObj){
if(obj holds condition){
add the descriptor of the obj (in string format) to an array (not the entire obj)
}
You can use Object.keys() to iterate over your object keys, then use Array.filter() to filter the keys, here I am checking if the inner objects have a property show and if this property is truthy:
const mainObj = {
obj1: { show: true, a: 1 },
obj2: { show: false, a: 2 },
obj3: { a: 3 },
obj4: { show: true, b: 1 }
};
const result = Object.keys(mainObj).filter(key => mainObj[key].show);
console.log(result);
If you want to use a for-in loop, you have to make sure the property is part of the object and is not inherited from its protype chain using Object.hasOwnProperty():
const mainObj = {
obj1: { show: true, a: 1 },
obj2: { show: false, a: 2 },
obj3: { a: 3 },
obj4: { show: true, b: 1 }
};
const result = [];
for (const prop in mainObj) {
if (mainObj.hasOwnProperty(prop) && mainObj[prop].show) {
result.push(prop);
}
}
console.log(result);
If I have 2 type of objects:
object1 : {
value : { foo1: {}, foo2: 5 }, state: true, etc = {}
}
And
object2 : {
value : { foo1: { value: 5}, foo2: 6 }, state: true, etc = {}
}
If I do object1=object2 what exactly happens with object1 on all levels please.
I'm going to simplify that a bit:
var a = { value: 1, aStuff: true };
var b = { value: 2, bStuff: true };
b = a;
console.log(b); // { value: 1, aStuff: true }
Now a and b reference the same object. Think of it like the same object is accessible by two names. Which means this happens when you change that object:
a.value = 5
console.log(a); // { value: 5, aStuff: true }
Two names, one object.
So what happened to what to the { value: 2, bStuff: true } object? Once you tell b to reference a different object then no existing variable has a reference to it, so eventually the garbage collector will find it and dispose of it.
What happens with inner objects? That is the question..
Nothing at all. The outer object still holds references the values it contains. All that's changed is that you have two variables pointing to that same outer object.
object1 is now a reference of object2, any change in object1, will change object2;
var object1 = { foo: 'bar' };
var object2 = {
value : { foo1: { value: 5}, foo2: 6 }
};
object1 = object2; // the { foo: 'bar' } is gone.
object1.foo2 = 7; //This changes object2.foo2 value
console.log(object2.foo2); //7
I am looking for a technique to run over a object of nested properties and wish to join the properties'.
This is the object I'd like to join:
var array = {
prop1: {
foo: function() {
// Your code here
}
},
prop2: {
bar1: 'some value',
bar2: 'some other value'
}
};
The result should look like this:
[
[ 'prop1', 'foo' ],
[ 'prop2', 'bar1' ],
[ 'prop2', 'bar2' ]
]
Then I'd like to join the array to strings formatted like this:
prop1.foo
prop2.bar1
prop2.bar2
Any tips?
EDIT: Forgot to say it should work for deeper arrays too.
Something along these lines? http://jsfiddle.net/X2X2b/
var array = {
prop1: {
foo: function() {
// Your code here
}
},
prop2: {
bar1: 'some value',
bar2: 'some other value'
}
};
var newA = [],
newB = [];
for ( var obj in array ) {
for (var inObj in array[obj]) {
newA.push([obj, inObj]);
newB.push(obj + '.' + inObj);
}
}
console.log(newA);
console.log(newB);
This is quite a different problem now that you have specified that it needs to support arbitrary depths. In order to solve it we need to use recursion and we need to use a second recursive parameter which keeps track of where we are in the nested hierarchy.
function objectPropertiesToArrays(obj, prepend) {
// result will store the final list of arrays
var result = [];
// test to see if this is a valid object (code defensively)
if(obj != null && obj.constructor === Object) {
for (var propertyName in obj) {
var property = obj[propertyName],
// clone prepend instantiate a new array
list = (prepend || []).slice(0);
// add the property name to the list
list.push(propertyName);
// if it isn't a nested object, we're done
if (property.constructor !== Object) {
result.push(list);
// if it is a nested object, recurse
} else {
// recurse and append the resulting arrays to our list
result = result.concat(objectPropertiesToArrays(property, list));
}
}
}
return result;
}
Example:
var obj = {
prop1: {
foo: function() { }
},
prop2: {
bar1: 'some value',
bar2: 'some other value'
},
prop3: {
x: {
y: [],
z: 'test'
},
erg: 'yar'
}
};
objectPropertiesToArrays(obj);
Returns
[
["prop1", "foo"],
["prop2", "bar1"],
["prop2", "bar2"],
["prop3", "x", "y"],
["prop3", "x", "z"],
["prop3", "erg"]
]
I'm serializing objects to JSON strings with JavaScript,
I noticed only enumerable object properties get serialized:
var a = Object.create(null,{
x: { writable:true, configurable:true, value: "hello",enumerable:false },
y: { writable:true, configurable:true, value: "hello",enumerable:true }
});
document.write(JSON.stringify(a)); //result is {"y":"hello"}
[pen]
I'm wondering why that is? I've searched through the MDN page, the json2 parser documentation.
I could not find this behavior documented any-where.
I suspect this is the result of using for... in loops that only go through [[enumerable]] properties (at least in the case of json2). This can probably be done with something like Object.getOwnPropertyNames that returns both enumerable, and non-enumerable properties.
That might be problematic to serialize though (due to deserialization).
tl;dr
Why does JSON.stringify only serialize enumerable properties?
Is this behavior documented anywhere?
How can I implement serializing non-enumerable properties myself?
It's specified in the ES5 spec.
If Type(value) is Object, and IsCallable(value) is false
If the [[Class]] internal property of value is "Array" then
Return the result of calling the abstract operation JA with argument value.
Else, return the result of calling the abstract operation JO with argument value.
So, let's look at JO. Here's the relevant section:
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.
As #ThiefMaster answered above, it's specified in the spec
however, if you know the names of the non-enumerable properties you like to be serialized ahead of time, you can achieve it by passing a replacer function as the second param to JSON.stringify() (documentation on MDN), like so
var o = {
prop: 'propval',
}
Object.defineProperty(o, 'propHidden', {
value: 'propHiddenVal',
enumerable: false,
writable: true,
configurable: true
});
var s = JSON.stringify(o, (key, val) => {
if (!key) {
// Initially, the replacer function is called with an empty string as key representing the object being stringified. It is then called for each property on the object or array being stringified.
if (typeof val === 'object' && val.hasOwnProperty('propHidden')) {
Object.defineProperty(val, 'propHidden', {
value: val.propHidden,
enumerable: true,
writable: true,
configurable: true
});
}
}
return val;
});
console.log(s);
You can achieve a generic JSON.stringify() that includes non-enumerables with code like below. But first, some notes:
This code has not been thoroughly tested for possible bugs or unexpected behavior; also, it turns functions into basic objects. Treat accordingly.
copyEnumerable() is the function to pay attention to. It does not itself internally call JSON.stringify() (you can do that yourself, with the output object) mainly because recursive calls to JSON.stringify() (due to nested objects) will yield uglier, unwanted results.
I'm only checking for object and function types to be treated specially; I believe that these are the only types which need to be handled specially... Note that JS Arrays ([]) fall into this category (objects), as well as classes (functions), and null does not. Since null is of type object, I included the !! check.
The stopRecursiveCopy Set is just to make sure it's not doing infinite recursion.
I'm using a trick with the 2 extra parameters to JSON.stringify() calls to make it output something formatted prettier, for easy reading.
The code is in an easy format to try out and tweak in the console:
{
o = {};
d = {};
Object.defineProperties(o, {
a: {
value: 5,
enumerable: false
},
b: {
value: "test",
enumerable: false
},
c: {
value: {},
enumerable: false
}
});
Object.defineProperty(o.c, "d", {
value: 7,
enumerable: false
});
//The code to use (after careful consideration!).
function isObject(testMe) {
return ((typeof(testMe) === "object" && !!testMe) ||
typeof(testMe) === "function");
}
let stopRecursiveCopy = new Set();
function copyEnumerable(obj) {
if (!isObject(obj)) {
return obj;
}
let enumerableCopy = {};
for (let key of Object.getOwnPropertyNames(obj)) {
if (isObject(obj[key])) {
if (!stopRecursiveCopy.has(obj[key])) {
stopRecursiveCopy.add(obj[key]);
enumerableCopy[key] = copyEnumerable(obj[key]);
stopRecursiveCopy.delete(obj[key]);
}
} else {
enumerableCopy[key] = obj[key];
}
}
return enumerableCopy;
}
console.log(JSON.stringify(copyEnumerable(o), null, " "));
Object.defineProperty(copyEnumerable, "test", {
value: 10,
enumerable: false
});
console.log(JSON.stringify(copyEnumerable(copyEnumerable), null, " "));
Object.defineProperty(o, "f", {
value: copyEnumerable,
enumerable: false
});
console.log(JSON.stringify(copyEnumerable(o), null, " "));
}
which outputs:
//o
{
"a": 5,
"b": "test",
"c": {
"d": 7
}
}
//copyEnumerable itself
{
"test": 10,
"prototype": {
"constructor": {
"test": 10,
"length": 1,
"name": "copyEnumerable"
}
},
"length": 1,
"name": "copyEnumerable"
}
//o again, but with `f: copyEnumerable` added
{
"a": 5,
"b": "test",
"c": {
"d": 7
},
"f": {
"test": 10,
"prototype": {},
"length": 1,
"name": "copyEnumerable"
}
}
Pertinent links:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
https://stackoverflow.com/a/14706877/1599699
https://stackoverflow.com/a/18538851/1599699