This question already has answers here:
How can I merge properties of two JavaScript objects dynamically?
(69 answers)
Closed 6 years ago.
I want to update an object that could look like this:
currentObject = {
someValue : "value",
myObject : {
attribute1 : "foo",
attribute2 : "bar"
}
};
.. with an object that contains some changes i.e.:
updateObject = {
myObject : {
attribute2 : "hello world"
}
};
At the end I would like to have currentObject updated so that:
currentObject.myObject.attribute2 == "hello world"
That should be posible for other objects as well..
As a solution I thought about iterating over the object and somehow take care of the namespace. But I wonder if there is an easy solution for that problem by using a library like jQuery or prototype.
I suggest using underscore.js (or better, lo-dash) extend:
_.extend(destination, *sources)
Copy all of the properties in the source objects over to the
destination object, and return the destination object. It's in-order,
so the last source will override properties of the same name in
previous arguments.
_.extend({name: 'moe'}, {age: 50});
=> {name: 'moe', age: 50}
function update(obj/*, …*/) {
for (var i=1; i<arguments.length; i++) {
for (var prop in arguments[i]) {
var val = arguments[i][prop];
if (typeof val == "object") // this also applies to arrays or null!
update(obj[prop], val);
else
obj[prop] = val;
}
}
return obj;
}
should do the trick: update(currentObject, updateObject). You might want to add some type checks, like Object(obj) === obj to extend only real objects with real objects, use a correct loop for arrays or hasOwnProperty tests.
Here's an Object.keys and recursive example:
// execute object update function
update(currentObject, updateObject)
// instantiate object update function
function update (targetObject, obj) {
Object.keys(obj).forEach(function (key) {
// delete property if set to undefined or null
if ( undefined === obj[key] || null === obj[key] ) {
delete targetObject[key]
}
// property value is object, so recurse
else if (
'object' === typeof obj[key]
&& !Array.isArray(obj[key])
) {
// target property not object, overwrite with empty object
if (
!('object' === typeof targetObject[key]
&& !Array.isArray(targetObject[key]))
) {
targetObject[key] = {}
}
// recurse
update(targetObject[key], obj[key])
}
// set target property to update property
else {
targetObject[key] = obj[key]
}
})
}
JSFiddle demo (open console).
A simple implementation would look like this.
function copyInto(target /*, source1, sourcen */) {
if (!target || typeof target !== "object")
target = {};
if (arguments.length < 2)
return target;
for (var len = arguments.length - 1; len > 0; len--)
cloneObject(arguments[len-1], arguments[len]);
return target;
}
function cloneObject(target, source) {
if (!source || !target || typeof source !== "object" || typeof target !== "object")
throw new TypeError("Invalid argument");
for (var p in source)
if (source.hasOwnProperty(p))
if (source[p] && typeof source[p] === "object")
if (target[p] && typeof target[p] === "object")
cloneObject(target[p], source[p]);
else
target[p] = source[p];
else
target[p] = source[p];
}
This assumes no inherited properties should be cloned. It also does no checks for things like DOM objects, or boxed primitives.
We need to iterate in reverse through the arguments so that the copy is done in a right to left matter.
Then we make a separate cloneObject function to handle the recursive copying of nested objects in a manner that doesn't interfere with the right to left copying of the original object arguments.
It also ensures that the initial target is a plain object.
The cloneObject function will throw an error if a non-object was passed to it.
Related
EDIT: this was marked as a duplicate of a deep cloning question but my question (cf. title and last phrase) is about a way to tell if an object references itself, the deep cloning part is only there to provide context
I am trying to implement a function that will let me deep-copy a nested object without overwriting nested fields (I know lodash can solve this but I'd rather not use it of possible).
This is what I have written :
function copyObject(target, source) {
if (typeof target !== "object" || !target) {
throw 'target nust be a non-null javascript object';
}
Object.entries(source).map(([key, value]) => {
if (typeof value === "object"
&& typeof target[key] === "object"
&& target[key] !== null) {
copyObject(target[key], value);
} else {
target[key] = value;
}
})
return target;
}
the problem is this function would enter a infinite loop if its source parameter is an object that references itself like so (because it would always call itself on the c property of source) :
let src = {
a: "a",
b: "b",
}
src.c = src;
is there a way to know if a reference is part of an object ? I think in C this would be possible by looking at the memory addresses but in JS I don't know.
If I understand the question correctly, you should be good to go with the code bellow at the start of the .map func.
if( Object.is(value, source) ) {
return
}
MDN Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
Object.is() determines whether two values are the same value. Two values are the same if one of the following holds...
both the same object (meaning both values reference the same object in memory)
To give some background: By using Postman (the REST api tool) we are comparing XMLs to a template by converting the XMLs to JSON and compare those as Javascript objects. The comparison can handle wildcards in the values and will return a new JS object (or JSON) with only the differences. When there are no differences, I receive an empty object which is the correct state. In some cases empty values or objects are returned and we remove them from the object with a clean step.
This is how the clean function looks like:
Utils = {
clean: function(object) {
Object
.entries(object)
.forEach(([k, v]) => {
if (v && typeof v === 'object')
Utils.clean(v);
if (v && typeof v === 'object' && !Object.keys(v).length || v === null || v === undefined)
Array.isArray(object) ? object.splice(k, 1) : delete object[k];
});
return object;
}
}
This works fine for most cases except when we have an array with multiple the same empty object because of the object.splice in combination with the foreach as pointed out here.
Normally, I would use a filter function, use _.pickBy from lodash or iterate backwards through the array, but because of the layout of the clean function, I can not figure out how to do that.
Can you help me to point out what I need to do to remove multiple empty items and objects from an array correctly.
Real life testcase:
var x = {"Document":{"CstmrDrctDbtInitn":{"GrpHdr":{},"PmtInf":{"DrctDbtTxInf":[{"PmtId":{}},{"PmtId":{}},{"PmtId":{}},{"PmtId":{}},{"PmtId":{}}]}}}};
console.log(JSON.stringify(Utils.clean(x)));
// returns {"Document":{"CstmrDrctDbtInitn":{"PmtInf":{"DrctDbtTxInf":[{},{}]}}}}
// desired result: {}
Other testcases:
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{},{},{},{}]})));
// returns {"a":[null,"c",{},{},{}]}
// desired: {"a":["b", "c"]}
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{"d": {}},{}]})));
// returns {"a":[null,"c",{},{}]}
// desired: {"a":["b", "c"]}
console.log(JSON.stringify(Utils.clean({ "a" : [null,null,{"d": {}, "e": [null, {}]},{}]})));
// returns {"a":[null,{}]}
// desired: {}
Give this a shot, and here's a working example: https://jsfiddle.net/3rno4L7d/
Utils Object (with extra helpers)
const Utils = {
doDelete: function(val) {
return !Boolean(val) ||
Utils.isEmptyObj(val) ||
Utils.isEmptyArray(val);
},
isEmptyArray: function(val) {
return Array.isArray(val) && val.length === 0;
},
isEmptyObj: function(obj) {
return Object.keys(obj).length === 0 &&
obj.constructor === Object;
},
hasKeys: function(obj) {
return Object.keys(obj).length > 0;
},
clean: function(object) {
Object
.keys(object)
.forEach(key => {
const val = object[key];
// If dealing with an object, clean it.
if (val && typeof val === 'object') {
Utils.clean(val);
}
// If deleteable, delete and return
if (Utils.doDelete(val)) {
delete object[key];
return object;
}
// If array, loop over entries
if (Array.isArray(val)) {
let i = val.length;
// While lets us delete from the array without affecting the loop.
while (i--) {
let entry = val[i];
// If deleteable, delete from the array
if (Utils.doDelete(entry)) {
val.splice(i, 1)
} else if (Utils.hasKeys(entry)) {
// If an object, clean it
entry = Utils.clean(entry);
// Check to see if cleaned object is deleteable
if (Utils.doDelete(entry)) {
val.splice(i, 1)
}
}
}
// Once done with the array, check if deleteable
if (Utils.doDelete(val)) {
delete object[key];
}
}
});
return object;
}
}
Output
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{},{},{},{}]})));
// Returns {"a":["b","c"]}
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{"d": {}},{}]})));
// Returns {"a":["b","c"]}
console.log(JSON.stringify(Utils.clean({ "a" : [null,null,{"d": {}, "e": [null, {}]},{}]})));
// Returns {}
For debugging purposes, I would like to know (in runtime) all possible methods that I can call on a specific js variable.
This can be done in Chrome console (and others), but I like a javascript method that gives me an array with strings of the names of all methods that can be called on a variable.
If you use "for..in" or Object.keys, we dont get all method!
(If you call these on a variable containing a number, it doesn't include methods defined in Number.prototype!)
Any ideas?
Okay for others who have same problem I ended up writing this method.
var getKeys = function(obj){
var keys = [], k;
if (typeof obj !== "object" && typeof obj !== "function" || obj === null) {
//primitive
}else{
for (k in obj){
if(keys.indexOf(k) === -1){
keys.push(k);
}
}
}
var type = Object.prototype.toString.call(obj).slice(8, -1);
var prototype_keys = Object.getOwnPropertyNames(window[type].prototype);
for (k = 0; k<prototype_keys.length;k++){
if(keys.indexOf(prototype_keys[k]) === -1){
keys.push(prototype_keys[k]);
}
}
return keys;
};
Is there a way to filter out everything inside of a for...in loop to only get the objects?
I'm writing a function to loop through nested objects to find certain pieces of data and then save it to localStorage.
Example:
var equipped = {
data: [the rest of the properties of equipped go here],
tool: {
data: [the rest of the properties of tool go here],
axe: {
data: [the rest of the properties of axe go here],
iron: {...},
steel: {...}
}
}
}
The tool/axe/metal properties are all generated dynamically and is different each time. Inside the metal properties is the data I'm trying to save. I would normally just loop through the array if I was trying to access the data (Using knockoutjs for binding, it's much easier to just foreach the data array), but I'm using the variable from a for...in loop to build the rest of the tree in my localStorage object before stringifying it.
How I'm reading the object:
for (var type in equipped) {
if (check goes here) {
savedValue.equipped[type] = {};
for (var category in equipped[type]) {
etc etc...
}
}
}
I understand that everything is an object type so I can't just do an instanceof or typeof on a defined object to filter them out. Is there another easy way to do it inside of an if statement or do I have to make each step of the tree from a constructor so I can instanceof RealObject?
Either of these should do well:
function isObject(val) {
if (val === null) { return false;}
return (typeof val === 'object');
}
or
function isObject(obj) {
return obj === Object(obj);
}
or
// this only works with object literals
function isObject(val) {
return (!!val) && (val.constructor === Object);
};
this last one, gives me the following:
console.log(isObject()); // false
console.log(isObject([])); // false
console.log(isObject(new Date)); // false
console.log(isObject({})); // true
console.log(isObject(null)); // false
console.log(isObject(true)); // false
console.log(isObject(1)); // false
console.log(isObject('someValueString')); // false
so, something like:
for (var type in equipped) {
if (isObject(type)) {
savedValue.equipped[type] = {};
for (var category in equipped[type]) {
etc etc...
}
}
}
Note: You can also try the following, but I have not used it. So you'd have to go thru your use cases.
Object.getPrototypeOf
Here is the code to check whether the variable is object or not:
function isJsonObject( obj ) {
// Must be an Object.
// Because of IE, we also have to check the presence of the constructor property.
// Make sure that DOM nodes and window objects don't pass through, as well
if ( !obj || obj.toString() !== "[object Object]" || obj.nodeType || obj.setInterval ) {
return false;
}
// Not own constructor property must be Object
if ( obj.constructor
&& !obj.hasOwnProperty("constructor")
&& !obj.constructor.prototype.hasOwnProperty("isPrototypeOf")) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
var key;
for ( key in obj ) {}
return key === undefined || obj.hasOwnProperty( key );
}
There's an old hack for type detection I've used previously.
var classChecker = {}.toString;
classChecker.call({});
classChecker.call(function() {});
classChecker.call([]);
// etc...
// More immediately relevant use:
var savedValue = {
equipped: {}
};
var objectString = classChecker.call({});
for (var type in equipped) {
if (classChecker.call(equipped[type]) === objectString) {
savedValue.equipped[type] = {};
for (var category in equipped[type]) {
// ...
}
}
}
console.log(savedValue);
See http://plnkr.co/edit/nKLQsOdcurrpUCg7cOoJ?p=preview for a working sample. (open your console to view output)
Question about the implementation of the "each" function I found in the underscore.js source code (source below).
First, could someone explain what the line "else if (obj.length === +obj.length) " is checking for.
Second, could someone explain why hasOwnProperty.call(obj, key) is used, as opposed to obj.hasOwnProperty? Is it because the passed in obj may not implement hasOwnProperty (which I thought every javascript object did)
any insights appreciated. Thanks.
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
This:
+obj.length
...will do a toNumber conversion on the value of length.
It appears as though they're making sure that length references a number by doing the toNumber conversion, and verifying that it's still the same number after the conversion.
If so, they assume that it is an Array, or at least an Array-like object for iteration.
If not, they assume that enumeration of all key value pairs is desired.
var obj = {
length:null,
someprop:'some value'
};
obj.length === +obj.length; // false, so do the enumeration
var obj = {
length: 2,
"0":'some value',
"1":'some other value'
};
obj.length === +obj.length; // true, not an actual Array,
// but iteration is still probably wanted
Of course you could have an object with a length property that is a primitive number, but still intend to enumerate the properties.
var obj = {
length: 2,
"prop1":'some value',
"prop2":'some other value'
};
obj.length === +obj.length; // true, it will iterate, but it would
// seem that enumeration is intended