how to merge 2 arrays based on properties (not index) - javascript

As we know that jQuery.extend(true, obj1, obj2) method for deep copying the object's properties from obj2 to obj1. In case of array, it copies the property based on index. But I need copying based on some property (e.g. id in my case) as per following example:
obj1 = [{id:"id1", name:"name1"},{id:"id2", name:"name2"}]
obj2 = [{id:"id3", name:"name3"}{id:"id1", name:"name1_modified"}]
jQuery.extend will return:
[{id:"id3", name:"name3"}{id:"id1", name:"name1_modified"}]
But I need output as:
[{id:"id1", name:"name1_modified"},{id:"id2", name:"name2"}{id:"id3", name:"name3"}]
is there any method/library to accomplish this?

I have faced same problem while working on my project. So I jquery extend to accomplish array merge based on its property. If you want to merge array based on property, pass property name as last parameter in merge function. I have create a jsfiddle, see result in browser console.
function merge() {
var options, name, src, copy, copyIsArray, clone, targetKey, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false;
var currentId = typeof arguments[length - 1] == 'string' ? arguments[length - 1] : null;
if (currentId) {
length = length - 1;
}
// Handle a deep copy situation
if (typeof target === "boolean") {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep
// copy)
if (typeof target !== "object" && !jQuery.isFunction(target)) {
target = {};
}
// extend jQuery itself if only one argument is passed
if (length === i) {
target = this;
--i;
}
for (; i < length; i++) {
// Only deal with non-null/undefined values
if ((options = arguments[i]) != null) {
// Extend the base object
for (name in options) {
if (!options.hasOwnProperty(name)) {
continue;
}
copy = options[name];
var mm = undefined, src = undefined;
if (currentId && jQuery.isArray(options) && jQuery.isArray(target)) {
for (mm = 0; mm < target.length; mm++) {
if (currentId && (isSameString(target[mm][currentId], copy[currentId]))) {
src = target[mm];
break;
}
}
}
else {
src = target[name];
}
// Prevent never-ending loop
if (target === copy) {
continue;
}
targetKey = mm !== undefined ? mm : name;
// Recurse if we're merging plain objects or arrays
if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
}
else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
if (currentId) {
target[targetKey] = merge(deep, clone, copy, currentId);
}
else {
target[targetKey] = merge(deep, clone, copy);
}
// Don't bring in undefined values
}
else if (copy !== undefined) {
target[targetKey] = copy;
}
}
}
}
// Return the modified object
return target;
};
function isSameString (a , b){
return a && b && String(a).toLowerCase() === String(b).toLowerCase();
}
obj1 = [ {
id : "id1",
name : "name1"
}, {
id : "id2",
name : "name2"
} ]
obj2 = [ {
id : "id3",
name : "name3"
}, {
id : "id1",
name : "name1_modified"
} ];
console.log(merge(true, obj1, obj2, "id"));​

Related

Confirm number of occurrences of a objects in array

I'm having a huge difficulty in creating a program to check the number of occurrences of objects in array based on rules set by me. If a particular object exists more then one, I count the number of occurrences.
Example input:
[
'{"NAME1":{"12":{"10":1}}}',
'{"NAME1":{"12":{"10":1}}}',
'{"NAME1":{"12":{"10":1}}}'
]
Example output:
[
'{"NAME1":{"12":{"10":3}}}'
]
WARNING: THIS IS A EXAMPLE.
So, how i'm doing:
let SUPER = [
{"NAME1":{"12":{"10":1}}},
{"NAME1":{"12":{"10":1}}},
{"NAME1":{"12":{"10":1}}},
{"NAME1":{"12":{"10":1}}}
],
FINAL = [];
for (let _super of SUPER) {
_super = JSON.stringify(_super);
let ii = 0, ll = SUPER.length, number = 0;
for (ii; ii < ll; ii++) {
let current = JSON.stringify(SUPER[ii]);
if (_super === current) {
SUPER.splice(ii, 1);
number++;
}
}
if (number) {
FINAL.push(function clone(destination, source) {
destination = destination || {};
for (var prop in source) {
typeof source[prop] === 'object' && source[prop] !== null && source[prop]
? destination[prop] = clone({}, source[prop])
: destination[prop] = number
;
}
return destination;
}({}, JSON.parse(_super)));
}
}
document.body.innerHTML = JSON.stringify(FINAL, null, 4);
So, i'm looping over the SUPER two times, one inside the other to test each object, and if i find equal strings, i increase the number by one and remove the object from the array, then i use this script to assign the number to the innermost property of the object:
if (number) {
FINAL.push(function clone(destination, source) {
destination = destination || {};
for (var prop in source) {
typeof source[prop] === 'object' && source[prop] !== null && source[prop]
? destination[prop] = clone({}, source[prop])
: destination[prop] = number
;
}
return destination;
}({}, JSON.parse(_super)));
}
But isn't working properly because of a conflict in this line:
if (_super === current) {
SUPER.splice(ii, 1);
number++;
}
I'm messing up the loop. Any ideas how to fix? I'm open to suggestions, i don't know if there is a better way to achieve this, i hope someone knows.
Thanks.
Try writing your own equals function and find the occurences for those in the array.
If you are the one to set the "dynamically" changing rules then to perform this job is very easy by an invention of two Object methods; namely Object.prototype.getNestedValue() and Object.prototype.setNestedValue(). Lets do it. Both methods take a number of arguments which are the object properties (if the argument is "string" type) or array indices(if the argument is "number" type). In addition to that, in the Object.setNestedValues() the very last argument is the value to be set. Just simple.
So here it goes like this;
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
Object.prototype.setNestedValue = function(...a) {
return a.length > 2 ? typeof this[a[0]] === "object" && this[a[0]] !== null ? this[a[0]].setNestedValue(...a.slice(1))
: (this[a[0]] = typeof a[1] === "string" ? {} : new Array(a[1]),
this[a[0]].setNestedValue(...a.slice(1)))
: this[a[0]] = a[1];
};
var data = [
'{"NAME1":{"12":{"10":1}}}',
'{"NAME1":{"12":{"10":1}}}',
'{"NAME1":{"12":{"10":1}}}'
],
props = ["NAME1", "12", "10"], // this array is constructed dynamically !!
reduced = [data.reduce((p,c) => {var v = p.getNestedValue(...props);
p.setNestedValue(...props, !!v ? ++v : 1);
return p;},{})];
console.log(JSON.stringify(reduced,null,2));
When you splice the array, elements are shifted to left by 1 but the loop index continues to move forward not accounting for this shift. You can account for it by decrementing the current loop index by 1 each time you splice.
e.g:
t = [1,2,3,4];
console.log(t[1]); // output: 2
//when you splice
t.splice(1,1);
// after splicing the array elements are shifted
console.log(t[1]); //output: 3
Working solution:
let SUPER = [{
"NAME1": {
"12": {
"10": 1
}
}
}, {
"NAME1": {
"12": {
"10": 1
}
}
}, {
"NAME1": {
"12": {
"10": 1
}
}
}, {
"NAME1": {
"12": {
"10": 1
}
}
}],
FINAL = [];
for (let i = 0; i < SUPER.length; i++) {
let _super = JSON.stringify(SUPER[i]),
ii = i + 1,
ll = SUPER.length,
number = 1;
for (ii; ii < ll; ii++) {
let current = JSON.stringify(SUPER[ii]);
if (_super === current) {
SUPER.splice(ii, 1);
ii--;
number++;
}
}
if (number) {
FINAL.push(function clone(destination, source) {
destination = destination || {};
for (var prop in source) {
typeof source[prop] === 'object' && source[prop] !== null && source[prop] ? destination[prop] = clone({}, source[prop]) : destination[prop] = number;
}
return destination;
}({}, JSON.parse(_super)));
}
}
document.body.innerHTML = JSON.stringify(FINAL, null, 4);
I have also initialized ii, number to 1 to reduce one iteration of inner loop.

Javascript - Deep copy specific properties from one object to another new object

Suppose I have a source object that can be any arbitrary Javascript object, that may have properties that are themselves objects (or arrays or functions), nested to any level.
I want to perform a deep copy of this object into a new target object. However I only want to copy specific white-listed properties. These could be at any level in the source object.
Up till now I've been manually assigning white-listed properties in the source object to the target object. This doesn't seem very elegant and nor is it reusable. Can you please give me some guidance on implementing this using a more elegant and re-usable approach?
This should do what you are looking for, including circular references.
EDIT: keep in mind this will get slower and slower for objects with lots of circular references inside! The lookup to see if a reference has been seen is a simple scan.
var util = require('util')
var propertiesToCopy = {
'a' : true,
'b' : true,
'c' : true,
'd' : true,
'e' : true,
'f' : true,
'p1': true,
'p2': true,
'g' : true
};
var obj;
obj = {
p2 : {
a : 1,
b : 2,
c : {},
d : {
f : 2
}
},
p3 : 'hello'
};
// circular
obj.p1 = obj;
obj.p2.d.e = obj;
// sub-circular
obj.p2.g = obj.p2.c;
function getNewObjectFromObjects(obj, objects) {
for (var i = 0; i < objects.length; i++) {
if (obj === objects[i].old) return objects[i].new;
}
return false;
}
function whiteListedCopy(obj, whitelist, root, newRoot, objects) {
var cloned = {};
var keys = Object.keys(obj);
root = root || obj;
newRoot = newRoot || cloned;
objects = objects || [ {'old' : root, 'new': newRoot} ];
keys.forEach(function(val) {
if (whitelist[val] === true) {
if (typeof(obj[val]) === typeof({}) ||
typeof(obj[val]) === typeof([]) ) {
var reference = getNewObjectFromObjects(obj[val], objects);
if (reference === false) {
cloned[val] = whiteListedCopy(obj[val], whitelist, root, newRoot, objects);
objects.push({ 'old' : obj[val], 'new': cloned[val]});
} else {
cloned[val] = reference;
}
} else {
cloned[val] = obj[val];
}
}
});
return cloned;
}
var clonedObject = whiteListedCopy(obj, propertiesToCopy);
console.log(util.inspect(clonedObject));
console.log('assert c and g are same reference:', clonedObject.p2.g === clonedObject.p2.c);
console.log('assert p1 is circular:', clonedObject === clonedObject.p1);

Find object by properties from array

With an array, a value, and and an object with nested objects:
Object
mesh
Array
['options', 'range', 'x']
Value
12.5
Is it possible to translate this to update a property, e.g.
mesh.options.range.x = 12.5
Attempted:
index = (obj, i) ->
obj[i]
arr.reduce(index, obj) = 12.5
Update
Thank you all for the elegant solutions.
Using .reduce() is actually pretty nice for this:
// current object----| |----current key
// v v
arr.reduce(function(obj, key) {
return obj == null ? obj : obj[key];
}, window.mesh);
// ^
// |-- initial object
Your attempt to use .reduce() needed to pass a function that manages the "accumulation".
Here, as long as the previous obj wasn't null or undefined, it'll return the key of the current obj, which becomes the next obj.
Then since you need to assign a value, you'd actually want to get the value of the second to last key.
var o = arr.slice(0,-1).reduce(function(obj, key) {
return obj == null ? obj : obj[key];
}, window.mesh);
And then check its existence and use the last item in arr to do the assignment.
o && o[arr.pop()] = 12.5;
All of this can be abstracted away into a function that does one or the other based on how many arguments were passed.
function setFromArray(obj, arr, val) {
var keys = arguments.length < 3 ? arr.slice() : arr.slice(0, -1);
var o = keys.slice(0,-1).reduce(function(obj, key) {
return obj == null ? obj : obj[key];
}, window.mesh);
if (arguments.length < 3)
return o;
else
o && o[keys.pop()];
}
Here's a general solution:
function setPropertyPath(obj, path, value) {
var o = obj;
for (var i = 0; i < path.length - 1; i++) {
o = o[path[i]];
}
o[path[path.length - 1]] = value;
}
Usage:
var obj = { a: { b: { c: 0 } } };
setPropertyPath(obj, ['a', 'b', 'c'], 10);
console.log(obj.a.b.c); // prints '10'
JSBin
var mesh = {},
arr = ['options','range','x'],
value = 12.5;
mesh[arr[0]][arr[1]][arr[2]] = value;
If array length is static do something like this:
mesh[array[0]][array[1]][array[2]] = value;
However, one problem with this is that javascript doesn't do autovivification, so if you're accessing a key value that isn't previously defined you could run into errors (if mesh.options hasn't been defined then the above will throw an error because you can't assign to it). To solve that you might abstract this out into a function that handles things recursively:
http://jsfiddle.net/h4jVg/
function update_val(obj, array, val, prev) {
if (array.length == 0) {
obj = val;
return;
}
var cur = array.shift();
if(array.length == 0) {
obj[cur] = val;
return;
} else if (obj[cur] == undefined) {
obj[cur] = {};
}
update_val(obj[cur], array, val);
}

Creating a new object, not a reference [duplicate]

This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
How do I correctly clone a JavaScript object?
(81 answers)
Closed 9 years ago.
this is my first time here.
So the problem is, i have an object with all my variables like this:
app.Variables = {
var1: 0,
var2: 0,
var3: 0
}
And i want to store this values in a object called Defaults like this:
app.Defaults = app.Variables
But the problem now is, in my code, app.Variables.var1 e.g. get incremented like this:
app.Variables.var1++
And this means, that app.Defaults.var1 get also incremented equal to app.Variables.var1.
What shall i do here?
Simplest version is to use JSON.parse/stringify, fastest is to use a plain clone method:
/* simplest */
var clone = JSON.parse(JSON.stringify(obj));
/* fastest */
function clone(obj) {
if (obj == null ||typeof obj != "object") return obj;
var copy = obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
}
return copy;
}
var clone2 = clone(obj);
You could write a deep clone Method, which copies every value of every property of your Object to a new one.
Note i extend Object.prototype to avoid type checking and for simplicities sake, this could be changed, if you feel unpleasent with it
Object.defineProperty(Object.prototype, "clone", {
enumerable : false,
value: function(deep) {
deep |= 0;
var type = typeof this;
if (type !== "object") {
return this.valueOf();
}
var clone = {};
if (0 === deep) {
for (var prop in this) {
clone[prop] = this[prop];
}
} else {
for (var prop in this) {
if ( typeof this[prop] !== "undefined" && this[prop] !== null)
clone[prop] = ( typeof this[prop] !== "object" ? this[prop] : this[prop].clone(deep - 1));
else
clone[prop] = "";
}
}
return clone;
}
});
Object.defineProperty(Array.prototype, "clone", {
enumerable : false,
value:function(deep) {
deep |= 0;
var clone = [];
if (0 === deep)
clone = this.concat();
else
this.forEach(function(e) {
if ( typeof e !== "undefined" && e !== null)
clone.push(( typeof e !== "object" ? e : e.clone(deep - 1)));
else
clone.push("");
});
return clone;
}
});
Example output and a Demo
var first = {
var1:0,
var2:0
var3:0
};
var second = first.clone(Infinity);
first.var1++;
console.log (first.var1,second.var1,second); //1 , 0
To apply this to your code, you just have to clone the Object app.Defaults = app.Variables.clone()
The first argument is the level of deepness. If omitted, only the first level is cloned, which would be enough in this case.

jQuery function to compute the difference between two JavaScript objects

I have a rich AJAX-based web application that uses JQuery + Knockout. I have a JQuery plugin that wraps my Knockout view models to expose utility methods like .reset(), .isDirty(), and so on.
I have a method called .setBaseline() that essentially takes a snapshot of the data model once it has been populated (via the mapping plugin). Then I can use this snapshot to quickly determine if the model has changed.
What I'm looking for is some kind of general purpose function that can return an object that represents the differences between two 2 JavaScript objects where one of the objects is considered to be the master.
For example, assume that this is my snapshot:
var snapShot = {
name: "Joe",
address: "123 Main Street",
age: 30,
favoriteColorPriority: {
yellow: 1,
pink: 2,
blue: 3
}
};
Then assume that the live data looks like this:
var liveData = {
name: "Joseph",
address: "123 Main Street",
age: 30,
favoriteColorPriority: {
yellow: 1,
pink: 3,
blue: 2
}
};
I want a .getChanges(snapShot, liveData) utility function that returns the following:
var differences = {
name: "Joseph",
favoriteColorPriority: {
pink: 3,
blue: 2
}
};
I was hoping that the _.underscore library might have something like this, but I couldn't find anything that seemed to work like this.
I don't think there is such a function in underscore, but it's easy to implement yourself:
function getChanges(prev, now) {
var changes = {};
for (var prop in now) {
if (!prev || prev[prop] !== now[prop]) {
if (typeof now[prop] == "object") {
var c = getChanges(prev[prop], now[prop]);
if (! _.isEmpty(c) ) // underscore
changes[prop] = c;
} else {
changes[prop] = now[prop];
}
}
}
return changes;
}
or
function getChanges(prev, now) {
var changes = {}, prop, pc;
for (prop in now) {
if (!prev || prev[prop] !== now[prop]) {
if (typeof now[prop] == "object") {
if(c = getChanges(prev[prop], now[prop]))
changes[prop] = c;
} else {
changes[prop] = now[prop];
}
}
}
for (prop in changes)
return changes;
return false; // false when unchanged
}
This will not work with Arrays (or any other non-plain-Objects) or differently structured objects (removals, primitive to object type changes).
Posting my own answer so folks can see the final implementation that also works with arrays. In the code below, "um" is my namespace, and I'm also using the _.isArray() and _.isObject methods from Underscore.js.
The code that looks for "_KO" is used to skip past Knockout.js members that are present in the object.
// This function compares 'now' to 'prev' and returns a new JavaScript object that contains only
// differences between 'now' and 'prev'. If 'prev' contains members that are missing from 'now',
// those members are *not* returned. 'now' is treated as the master list.
um.utils.getChanges = function (prev, now) {
var changes = {};
var prop = {};
var c = {};
//-----
for (prop in now) { //ignore jslint
if (prop.indexOf("_KO") > -1) {
continue; //ignore jslint
}
if (!prev || prev[prop] !== now[prop]) {
if (_.isArray(now[prop])) {
changes[prop] = now[prop];
}
else if (_.isObject(now[prop])) {
// Recursion alert
c = um.utils.getChanges(prev[prop], now[prop]);
if (!_.isEmpty(c)) {
changes[prop] = c;
}
} else {
changes[prop] = now[prop];
}
}
}
return changes;
};
I am using JsonDiffPatch in my projects to find the delta, transfer it over the net, and then patch the object at the other end to get the exact copy. It is very easy to use and works really well.
And it works with arrays too!
Solution with the use of jQuery.isEmptyObject()
This reworks the getChanges(prev, now) solution above. It should work also on objects of different type and also when prev[prop] is undefined (resulting to now[prop])
function getChanges(prev, now)
{
// sanity checks, now and prev must be an objects
if (typeof now !== "object")
now = {};
if (typeof prev !== "object")
return now;
var changes = {};
for (var prop in now) {
// if prop is new in now, add it to changes
if (!prev || !prev.hasOwnProperty(prop) ) {
changes[prop] = now[prop];
continue;
}
// if prop has another type or value (different object or literal)
if (prev[prop] !== now[prop]) {
if (typeof now[prop] === "object") {
// prop is an object, do recursion
var c = getChanges(prev[prop], now[prop]);
if (!$.isEmptyObject(c))
changes[prop] = c;
} else {
// now[prop] has different but literal value
changes[prop] = now[prop];
}
}
}
// returns empty object on none change
return changes;
}
No need to use jquery for this. I recently wrote a module to do this: https://github.com/Tixit/odiff . Here's an example:
var a = [{a:1,b:2,c:3}, {x:1,y: 2, z:3}, {w:9,q:8,r:7}]
var b = [{a:1,b:2,c:3},{t:4,y:5,u:6},{x:1,y:'3',z:3},{t:9,y:9,u:9},{w:9,q:8,r:7}]
var diffs = odiff(a,b)
/* diffs now contains:
[{type: 'add', path:[], index: 2, vals: [{t:9,y:9,u:9}]},
{type: 'set', path:[1,'y'], val: '3'},
{type: 'add', path:[], index: 1, vals: [{t:4,y:5,u:6}]}
]
*/
In the readme for odiff, I listed out other modules that I determined didn't fit my needs if you want to check those out too.
Based on the solution above. This version corrects a bug, some changes are not detected with the original solution.
getChanges = function (prev, now) {
var changes = {}, prop, pc;
for (prop in now) {
if (!prev || prev[prop] !== now[prop]) {
if (typeof now[prop] == "object") {
if (c = getChanges(prev[prop], now[prop]))
changes[prop] = c;
} else {
changes[prop] = now[prop];
}
}
}
for (prop in prev) {
if (!now || now[prop] !== prev[prop]) {
if (typeof prev[prop] == "object") {
if (c = getChanges(now[prop], prev[prop]))
changes[prop] = c;
} else {
changes[prop] = prev[prop];
}
}
}
return changes;
};
Be aware that typeof now[prop] == "object" is even true if now[prop] is a Date.
Instead of:
typeof now[prop] == "object"
I used:
Object.prototype.toString.call( now[prop] === "[object Object]")

Categories

Resources