Merge JS objects without overwriting - javascript

Suppose you have two objects:
var foo = {
a : 1,
b : 2
};
var bar = {
a : 3,
b : 4
}
What's the best way to merge them (and allow deep merging) to create this:
var foobar = {
a : [1, 3],
b : [2, 4]
}
Edit for question clarification: Ideally, in the case of an existing property in one and not the other, I would expect an array to still be created, for normalization purposes and to allow for further reduction of the map, however the answers I'm seeing below are more than sufficient. For the purposes of this exercise, I was only looking for string or numerical merges, so I hadn't entertained every possible situational case. If you held a gun to my head and asked me to make a choice, though, I'd say default to arrays.
Thanks all for your contributions.

This ought to do what you're looking for. It will recursively merge arbitrarily deep objects into arrays.
// deepmerge by Zachary Murray (dremelofdeath) CC-BY-SA 3.0
function deepmerge(foo, bar) {
var merged = {};
for (var each in bar) {
if (foo.hasOwnProperty(each) && bar.hasOwnProperty(each)) {
if (typeof(foo[each]) == "object" && typeof(bar[each]) == "object") {
merged[each] = deepmerge(foo[each], bar[each]);
} else {
merged[each] = [foo[each], bar[each]];
}
} else if(bar.hasOwnProperty(each)) {
merged[each] = bar[each];
}
}
for (var each in foo) {
if (!(each in bar) && foo.hasOwnProperty(each)) {
merged[each] = foo[each];
}
}
return merged;
}
And this one will do the same, except that the merged object will include copies of inherited properties. This probably isn't what you're looking for (as per RobG's comments below), but if that is actually what you are looking for, then here it is:
// deepmerge_inh by Zachary Murray (dremelofdeath) CC-BY-SA 3.0
function deepmerge_inh(foo, bar) {
var merged = {};
for (var each in bar) {
if (each in foo) {
if (typeof(foo[each]) == "object" && typeof(bar[each]) == "object") {
merged[each] = deepmerge(foo[each], bar[each]);
} else {
merged[each] = [foo[each], bar[each]];
}
} else {
merged[each] = bar[each];
}
}
for (var each in foo) {
if (!(each in bar)) {
merged[each] = foo[each];
}
}
return merged;
}
I tried it out with your example on http://jsconsole.com, and it worked fine:
deepmerge(foo, bar)
{"a": [1, 3], "b": [2, 4]}
bar
{"a": 3, "b": 4}
foo
{"a": 1, "b": 2}
Slightly more complicated objects worked as well:
deepmerge(as, po)
{"a": ["asdf", "poui"], "b": 4, "c": {"q": [1, 444], "w": [function () {return 5;}, function () {return 1123;}]}, "o": {"b": {"t": "cats"}, "q": 7}, "p": 764}
po
{"a": "poui", "c": {"q": 444, "w": function () {return 1123;}}, "o": {"b": {"t": "cats"}, "q": 7}, "p": 764}
as
{"a": "asdf", "b": 4, "c": {"q": 1, "w": function () {return 5;}}}

Presumably you would iterate over one object and copy its property names to a new object and values to arrays assigned to those properties. Iterate over subsequent objects, adding properties and arrays if they don't already exist or adding their values to existing properties and arrays.
e.g.
function mergeObjects(a, b, c) {
c = c || {};
var p;
for (p in a) {
if (a.hasOwnProperty(p)) {
if (c.hasOwnProperty(p)) {
c[p].push(a[p]);
} else {
c[p] = [a[p]];
}
}
}
for (p in b) {
if (b.hasOwnProperty(p)) {
if (c.hasOwnProperty(p)) {
c[p].push(b[p]);
} else {
c[p] = [b[p]];
}
}
}
return c;
}
You could modify it to handle any number of objects by iterating over the arguments supplied, but that would make passing the object to merge into more difficult.

https://lodash.com/docs/3.10.1#merge
// using a customizer callback
var object = {
'fruits': ['apple'],
'vegetables': ['beet']
};
var other = {
'fruits': ['banana'],
'vegetables': ['carrot']
};
_.merge(object, other, function(a, b) {
if (_.isArray(a)) {
return a.concat(b);
}
});
// → { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }

I just leave it here.
Shallow merging with values as arrays.
const foo = {a: 1, b: 2}
const bar = {a: 2, с: 4}
const baz = {a: 3, b: 3}
const myMerge = (...args) => args
.flatMap(Object.entries)
.reduce((acc, [key, value]) => {
acc[key] ??= []
acc[key].push(value)
return acc
}, {})
console.log(myMerge(foo, bar, baz))
//{ a: [1, 2, 3],
// b: [2, 3],
// с: [4] }
.as-console-wrapper { max-height: 100% !important; top: 0 }

Related

Comparing items in ojects in two arrays js [duplicate]

I want to compare 2 arrays of objects in JavaScript code. The objects have 8 total properties, but each object will not have a value for each, and the arrays are never going to be any larger than 8 items each, so maybe the brute force method of traversing each and then looking at the values of the 8 properties is the easiest way to do what I want to do, but before implementing, I wanted to see if anyone had a more elegant solution. Any thoughts?
As serialization doesn't work generally (only when the order of properties matches: JSON.stringify({a:1,b:2}) !== JSON.stringify({b:2,a:1})) you have to check the count of properties and compare each property as well:
const objectsEqual = (o1, o2) =>
Object.keys(o1).length === Object.keys(o2).length
&& Object.keys(o1).every(p => o1[p] === o2[p]);
const obj1 = { name: 'John', age: 33};
const obj2 = { age: 33, name: 'John' };
const obj3 = { name: 'John', age: 45 };
console.log(objectsEqual(obj1, obj2)); // true
console.log(objectsEqual(obj1, obj3)); // false
If you need a deep comparison, you can call the function recursively:
const obj1 = { name: 'John', age: 33, info: { married: true, hobbies: ['sport', 'art'] } };
const obj2 = { age: 33, name: 'John', info: { hobbies: ['sport', 'art'], married: true } };
const obj3 = { name: 'John', age: 33 };
const objectsEqual = (o1, o2) =>
typeof o1 === 'object' && Object.keys(o1).length > 0
? Object.keys(o1).length === Object.keys(o2).length
&& Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
: o1 === o2;
console.log(objectsEqual(obj1, obj2)); // true
console.log(objectsEqual(obj1, obj3)); // false
Then it's easy to use this function to compare objects in arrays:
const arr1 = [obj1, obj1];
const arr2 = [obj1, obj2];
const arr3 = [obj1, obj3];
const arraysEqual = (a1, a2) =>
a1.length === a2.length && a1.every((o, idx) => objectsEqual(o, a2[idx]));
console.log(arraysEqual(arr1, arr2)); // true
console.log(arraysEqual(arr1, arr3)); // false
EDIT: You cannot overload operators in current, common browser-based implementations of JavaScript interpreters.
To answer the original question, one way you could do this, and mind you, this is a bit of a hack, simply serialize the two arrays to JSON and then compare the two JSON strings. That would simply tell you if the arrays are different, obviously you could do this to each of the objects within the arrays as well to see which ones were different.
Another option is to use a library which has some nice facilities for comparing objects - I use and recommend MochiKit.
EDIT: The answer kamens gave deserves consideration as well, since a single function to compare two given objects would be much smaller than any library to do what I suggest (although my suggestion would certainly work well enough).
Here is a naïve implemenation that may do just enough for you - be aware that there are potential problems with this implementation:
function objectsAreSame(x, y) {
var objectsAreSame = true;
for(var propertyName in x) {
if(x[propertyName] !== y[propertyName]) {
objectsAreSame = false;
break;
}
}
return objectsAreSame;
}
The assumption is that both objects have the same exact list of properties.
Oh, and it is probably obvious that, for better or worse, I belong to the only-one-return-point camp. :)
Honestly, with 8 objects max and 8 properties max per object, your best bet is to just traverse each object and make the comparisons directly. It'll be fast and it'll be easy.
If you're going to be using these types of comparisons often, then I agree with Jason about JSON serialization...but otherwise there's no need to slow down your app with a new library or JSON serialization code.
I know this is an old question and the answers provided work fine ... but this is a bit shorter and doesn't require any additional libraries ( i.e. JSON ):
function arraysAreEqual(ary1,ary2){
return (ary1.join('') == ary2.join(''));
}
I have worked a bit on a simple algorithm to compare contents of two objects and return an intelligible list of difference. Thought I would share. It borrows some ideas for jQuery, namely the map function implementation and the object and array type checking.
It returns a list of "diff objects", which are arrays with the diff info. It's very simple.
Here it is:
// compare contents of two objects and return a list of differences
// returns an array where each element is also an array in the form:
// [accessor, diffType, leftValue, rightValue ]
//
// diffType is one of the following:
// value: when primitive values at that index are different
// undefined: when values in that index exist in one object but don't in
// another; one of the values is always undefined
// null: when a value in that index is null or undefined; values are
// expressed as boolean values, indicated wheter they were nulls
// type: when values in that index are of different types; values are
// expressed as types
// length: when arrays in that index are of different length; values are
// the lengths of the arrays
//
function DiffObjects(o1, o2) {
// choose a map() impl.
// you may use $.map from jQuery if you wish
var map = Array.prototype.map?
function(a) { return Array.prototype.map.apply(a, Array.prototype.slice.call(arguments, 1)); } :
function(a, f) {
var ret = new Array(a.length), value;
for ( var i = 0, length = a.length; i < length; i++ )
ret[i] = f(a[i], i);
return ret.concat();
};
// shorthand for push impl.
var push = Array.prototype.push;
// check for null/undefined values
if ((o1 == null) || (o2 == null)) {
if (o1 != o2)
return [["", "null", o1!=null, o2!=null]];
return undefined; // both null
}
// compare types
if ((o1.constructor != o2.constructor) ||
(typeof o1 != typeof o2)) {
return [["", "type", Object.prototype.toString.call(o1), Object.prototype.toString.call(o2) ]]; // different type
}
// compare arrays
if (Object.prototype.toString.call(o1) == "[object Array]") {
if (o1.length != o2.length) {
return [["", "length", o1.length, o2.length]]; // different length
}
var diff =[];
for (var i=0; i<o1.length; i++) {
// per element nested diff
var innerDiff = DiffObjects(o1[i], o2[i]);
if (innerDiff) { // o1[i] != o2[i]
// merge diff array into parent's while including parent object name ([i])
push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + i + "]" + o[0]; return o; }));
}
}
// if any differences were found, return them
if (diff.length)
return diff;
// return nothing if arrays equal
return undefined;
}
// compare object trees
if (Object.prototype.toString.call(o1) == "[object Object]") {
var diff =[];
// check all props in o1
for (var prop in o1) {
// the double check in o1 is because in V8 objects remember keys set to undefined
if ((typeof o2[prop] == "undefined") && (typeof o1[prop] != "undefined")) {
// prop exists in o1 but not in o2
diff.push(["[" + prop + "]", "undefined", o1[prop], undefined]); // prop exists in o1 but not in o2
}
else {
// per element nested diff
var innerDiff = DiffObjects(o1[prop], o2[prop]);
if (innerDiff) { // o1[prop] != o2[prop]
// merge diff array into parent's while including parent object name ([prop])
push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + prop + "]" + o[0]; return o; }));
}
}
}
for (var prop in o2) {
// the double check in o2 is because in V8 objects remember keys set to undefined
if ((typeof o1[prop] == "undefined") && (typeof o2[prop] != "undefined")) {
// prop exists in o2 but not in o1
diff.push(["[" + prop + "]", "undefined", undefined, o2[prop]]); // prop exists in o2 but not in o1
}
}
// if any differences were found, return them
if (diff.length)
return diff;
// return nothing if objects equal
return undefined;
}
// if same type and not null or objects or arrays
// perform primitive value comparison
if (o1 != o2)
return [["", "value", o1, o2]];
// return nothing if values are equal
return undefined;
}
I tried JSON.stringify() and worked for me.
let array1 = [1,2,{value:'alpha'}] , array2 = [{value:'alpha'},'music',3,4];
JSON.stringify(array1) // "[1,2,{"value":"alpha"}]"
JSON.stringify(array2) // "[{"value":"alpha"},"music",3,4]"
JSON.stringify(array1) === JSON.stringify(array2); // false
There is a optimized code for case when function needs to equals to empty arrays (and returning false in that case)
const objectsEqual = (o1, o2) => {
if (o2 === null && o1 !== null) return false;
return o1 !== null && typeof o1 === 'object' && Object.keys(o1).length > 0 ?
Object.keys(o1).length === Object.keys(o2).length &&
Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
: (o1 !== null && Array.isArray(o1) && Array.isArray(o2) && !o1.length &&
!o2.length) ? true : o1 === o2;
}
Here is my attempt, using Node's assert module + npm package object-hash.
I suppose that you would like to check if two arrays contain the same objects, even if those objects are ordered differently between the two arrays.
var assert = require('assert');
var hash = require('object-hash');
var obj1 = {a: 1, b: 2, c: 333},
obj2 = {b: 2, a: 1, c: 444},
obj3 = {b: "AAA", c: 555},
obj4 = {c: 555, b: "AAA"};
var array1 = [obj1, obj2, obj3, obj4];
var array2 = [obj3, obj2, obj4, obj1]; // [obj3, obj3, obj2, obj1] should work as well
// calling assert.deepEquals(array1, array2) at this point FAILS (throws an AssertionError)
// even if array1 and array2 contain the same objects in different order,
// because array1[0].c !== array2[0].c
// sort objects in arrays by their hashes, so that if the arrays are identical,
// their objects can be compared in the same order, one by one
var array1 = sortArrayOnHash(array1);
var array2 = sortArrayOnHash(array2);
// then, this should output "PASS"
try {
assert.deepEqual(array1, array2);
console.log("PASS");
} catch (e) {
console.log("FAIL");
console.log(e);
}
// You could define as well something like Array.prototype.sortOnHash()...
function sortArrayOnHash(array) {
return array.sort(function(a, b) {
return hash(a) > hash(b);
});
}
My practice implementation with sorting, tested and working.
const obj1 = { name: 'John', age: 33};
const obj2 = { age: 33, name: 'John' };
const obj3 = { name: 'John', age: 45 };
const equalObjs = ( obj1, obj2 ) => {
let keyExist = false;
for ( const [key, value] of Object.entries(obj1) ) {
// Search each key in reference object and attach a callback function to
// compare the two object keys
if( Object.keys(obj2).some( ( e ) => e == key ) ) {
keyExist = true;
}
}
return keyExist;
}
console.info( equalObjs( obj1, obj2 ) );
Compare your arrays
// Sort Arrays
var arr1 = arr1.sort(( a, b ) => {
var fa = Object.keys(a);
var fb = Object.keys(b);
if (fa < fb) {
return -1;
}
if (fa > fb) {
return 1;
}
return 0;
});
var arr2 = arr2.sort(( a, b ) => {
var fa = Object.keys(a);
var fb = Object.keys(b);
if (fa < fb) {
return -1;
}
if (fa > fb) {
return 1;
}
return 0;
});
const equalArrays = ( arr1, arr2 ) => {
// If the arrays are different length we an eliminate immediately
if( arr1.length !== arr2.length ) {
return false;
} else if ( arr1.every(( obj, index ) => equalObjs( obj, arr2[index] ) ) ) {
return true;
} else {
return false;
}
}
console.info( equalArrays( arr1, arr2 ) );
I am sharing my compare function implementation as it might be helpful for others:
/*
null AND null // true
undefined AND undefined // true
null AND undefined // false
[] AND [] // true
[1, 2, 'test'] AND ['test', 2, 1] // true
[1, 2, 'test'] AND ['test', 2, 3] // false
[undefined, 2, 'test'] AND ['test', 2, 1] // false
[undefined, 2, 'test'] AND ['test', 2, undefined] // true
[[1, 2], 'test'] AND ['test', [2, 1]] // true
[1, 'test'] AND ['test', [2, 1]] // false
[[2, 1], 'test'] AND ['test', [2, 1]] // true
[[2, 1], 'test'] AND ['test', [2, 3]] // false
[[[3, 4], 2], 'test'] AND ['test', [2, [3, 4]]] // true
[[[3, 4], 2], 'test'] AND ['test', [2, [5, 4]]] // false
[{x: 1, y: 2}, 'test'] AND ['test', {x: 1, y: 2}] // true
1 AND 1 // true
{test: 1} AND ['test', 2, 1] // false
{test: 1} AND {test: 1} // true
{test: 1} AND {test: 2} // false
{test: [1, 2]} AND {test: [1, 2]} // true
{test: [1, 2]} AND {test: [1]} // false
{test: [1, 2], x: 1} AND {test: [1, 2], x: 2} // false
{test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 5}]} // true
{test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 6}]} // false
*/
function is_equal(x, y) {
const
arr1 = x,
arr2 = y,
is_objects_equal = function (obj_x, obj_y) {
if (!(
typeof obj_x === 'object' &&
Object.keys(obj_x).length > 0
))
return obj_x === obj_y;
return Object.keys(obj_x).length === Object.keys(obj_y).length &&
Object.keys(obj_x).every(p => is_objects_equal(obj_x[p], obj_y[p]));
}
;
if (!( Array.isArray(arr1) && Array.isArray(arr2) ))
return (
arr1 && typeof arr1 === 'object' &&
arr2 && typeof arr2 === 'object'
)
? is_objects_equal(arr1, arr2)
: arr1 === arr2;
if (arr1.length !== arr2.length)
return false;
for (const idx_1 of arr1.keys())
for (const idx_2 of arr2.keys())
if (
(
Array.isArray(arr1[idx_1]) &&
this.is_equal(arr1[idx_1], arr2[idx_2])
) ||
is_objects_equal(arr1[idx_1], arr2[idx_2])
)
{
arr2.splice(idx_2, 1);
break;
}
return !arr2.length;
}
Please try this one:
function used_to_compare_two_arrays(a, b)
{
// This block will make the array of indexed that array b contains a elements
var c = a.filter(function(value, index, obj) {
return b.indexOf(value) > -1;
});
// This is used for making comparison that both have same length if no condition go wrong
if (c.length !== a.length) {
return 0;
} else{
return 1;
}
}
The objectsAreSame function mentioned in #JasonBunting's answer works fine for me. However, there's a little problem: If x[propertyName] and y[propertyName] are objects (typeof x[propertyName] == 'object'), you'll need to call the function recursively in order to compare them.
not sure about the performance ... will have to test on big objects .. however, this works great for me.. the advantage it has compared to the other solutions is, the objects/array do not have to be in the same order ....
it practically takes the first object in the first array, and scans the second array for every objects .. if it's a match, it will proceed to another
there is absolutely a way for optimization but it's working :)
thx to #ttulka I got inspired by his work ... just worked on it a little bit
const objectsEqual = (o1, o2) => {
let match = false
if(typeof o1 === 'object' && Object.keys(o1).length > 0) {
match = (Object.keys(o1).length === Object.keys(o2).length && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p])))
}else {
match = (o1 === o2)
}
return match
}
const arraysEqual = (a1, a2) => {
let finalMatch = []
let itemFound = []
if(a1.length === a2.length) {
finalMatch = []
a1.forEach( i1 => {
itemFound = []
a2.forEach( i2 => {
itemFound.push(objectsEqual(i1, i2))
})
finalMatch.push(itemFound.some( i => i === true))
})
}
return finalMatch.every(i => i === true)
}
const ar1 = [
{ id: 1, name: "Johnny", data: { body: "Some text"}},
{ id: 2, name: "Jimmy"}
]
const ar2 = [
{name: "Jimmy", id: 2},
{name: "Johnny", data: { body: "Some text"}, id: 1}
]
console.log("Match:",arraysEqual(ar1, ar2))
jsfiddle: https://jsfiddle.net/x1pubs6q/
or just use lodash :))))
const _ = require('lodash')
const isArrayEqual = (x, y) => {
return _.isEmpty(_.xorWith(x, y, _.isEqual));
};
using _.some from lodash: https://lodash.com/docs/4.17.11#some
const array1AndArray2NotEqual =
_.some(array1, (a1, idx) => a1.key1 !== array2[idx].key1
|| a1.key2 !== array2[idx].key2
|| a1.key3 !== array2[idx].key3);
There`s my solution. It will compare arrays which also have objects and arrays. Elements can be stay in any positions.
Example:
const array1 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]];
const array2 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]];
const arraysCompare = (a1, a2) => {
if (a1.length !== a2.length) return false;
const objectIteration = (object) => {
const result = [];
const objectReduce = (obj) => {
for (let i in obj) {
if (typeof obj[i] !== 'object') {
result.push(`${i}${obj[i]}`);
} else {
objectReduce(obj[i]);
}
}
};
objectReduce(object);
return result;
};
const reduceArray1 = a1.map(item => {
if (typeof item !== 'object') return item;
return objectIteration(item).join('');
});
const reduceArray2 = a2.map(item => {
if (typeof item !== 'object') return item;
return objectIteration(item).join('');
});
const compare = reduceArray1.map(item => reduceArray2.includes(item));
return compare.reduce((acc, item) => acc + Number(item)) === a1.length;
};
console.log(arraysCompare(array1, array2));
This is work for me to compare two array of objects without taking into consideration the order of the items
const collection1 = [
{ id: "1", name: "item 1", subtitle: "This is a subtitle", parentId: "1" },
{ id: "2", name: "item 2", parentId: "1" },
{ id: "3", name: "item 3", parentId: "1" },
]
const collection2 = [
{ id: "3", name: "item 3", parentId: "1" },
{ id: "2", name: "item 2", parentId: "1" },
{ id: "1", name: "item 1", subtitle: "This is a subtitle", parentId: "1" },
]
const contains = (arr, obj) => {
let i = arr.length;
while (i--) {
if (JSON.stringify(arr[i]) === JSON.stringify(obj)) {
return true;
}
}
return false;
}
const isEqual = (obj1, obj2) => {
let n = 0
if (obj1.length !== obj2.length) {
return false;
}
for (let i = 0; i < obj1.length; i++) {
if (contains(obj2, obj1[i])) {
n++
}
}
return n === obj1.length
}
console.log(isEqual(collection1,collection2))
if you take into consideration the order of the items use built in function in lodash isEqual
comparing with json is pretty bad. try this package to compare nested arrays and get the difference.
https://www.npmjs.com/package/deep-object-diff
If you stringify them...
type AB = {
nome: string;
}
const a: AB[] = [{ nome: 'Célio' }];
const b: AB[] = [{ nome: 'Célio' }];
console.log(a === b); // false
console.log(JSON.stringify(a) === JSON.stringify(b)); // true

Create duplicate array of object

Say I have an array of object::
const banana = [{"a":"ann","b":"bann","det":[{"c":"cat","d":"dog"},{"c":"conn","d":"donn"}]}, {"a":"auu","b":"buu","det":[{"c":"camel","d":"damel"},{"c":"coww","d":"doww"}]}]
I want to transform this array of object in this form::
const banana = [{"a":"ann","b":"bann","c":"cat","d":"dog"}, {"a":"ann","b":"bann","c":"conn","d":"donn"}, {"a":"auu","b":"buu","c":"camel","d":"damel"}, {"a":"auu","b":"buu","c":"coww","d":"doww"}]
As you can see array of object inside array of object have merged and duplicated.
I tried as:
const apple = []
for(let i = 0; i<banana.length;i++){
for(let j = 0;j<banana[i].det.length;j++{
apple.push(banana[i].det[j])
}
}
console.log(apple)
**OUTPUT: [{c: "cat", d: "dog"},{c: "conn", d: "donn"},{c: "camel", d: "damel"},{c: "coww", d: "doww"}]**
But I'm looking for the O/P as:
[{"a":"ann","b":"bann","c":"cat","d":"dog"}, {"a":"ann","b":"bann","c":"conn","d":"donn"},
{"a":"auu","b":"buu","c":"camel","d":"damel"}, {"a":"auu","b":"buu","c":"coww","d":"doww"}]
But I'm unable to form logic. I'm still trying but if i could get some guidance that would be really helpful.
**EDIT:**So I've come up with an idea using spread operator:
let enamel = {}
for(let i = 0; i<banana.length;i++){
for(let j = 0;j<banana[i].det.length;j++){
employee = {
...banana[j],
...banana[i].det[j]
};
}
}
It gives the output as:
console.log(enamel)
{a: "auu", b: "buu", det: Array(2), c: "coww", d: "doww"}
But I want to have all the objects in an array as previously stated.
You can use this logic, which copies over initial object, adds extra properties, drops the det array, and flatten the result
function extras(obj) {
// create a copy of the current context (initial obj)
// and add all properties from the extra object
obj = Object.assign({}, this, obj);
// but delete the `det` from the copy
delete obj.det;
// and return the object
return obj;
}
// per each array object ...
banana
.map(
// and per each det ...
obj => obj.det.map(extras, obj)
)
// flatten the final array of objects
.flat();
You just have to extract a and b from object in banana. I have used destructuring to extract it.
const banana = [{ "a": "ann", "b": "bann", "det": [{ "c": "cat", "d": "dog" }, { "c": "conn", "d": "donn" }] }, { "a": "auu", "b": "buu", "det": [{ "c": "camel", "d": "damel" }, { "c": "coww", "d": "doww" }] }]
const apple = []
for (let i = 0; i < banana.length; i++) {
for (let j = 0; j < banana[i].det.length; j++) {
const {a,b} = banana[i];
const {c,d} = banana[i].det[j];
apple.push({a,b,c,d});
}
}
console.log(apple)
You can do this:
const banana = [
{
"a": "ann",
"b": "bann",
"det": [{ "c": "cat", "d": "dog" }, { "c": "conn", "d": "donn" }]
},
{
"a": "auu",
"b": "buu",
"det": [
{ "c": "camel", "d": "damel" },
{ "c": "coww", "d": "doww" }
]
}
]
const result = [];
banana.forEach( b =>{
b.det.forEach(d =>{
result.push({
a: b.a,
b: b.b,
c: d.c,
d: d.d
});
});
});
console.log(result);
Try this
const banana = [{"a":"ann","b":"bann","det":[{"c":"cat","d":"dog"},{"c":"conn","d":"donn"}]}, {"a":"auu","b":"buu","det":[{"c":"camel","d":"damel"},{"c":"coww","d":"doww"}]}]
const output = []
for (const { a, b, det } of banana) {
for (const animal of det) {
output.push({a, b, ...animal })
}
}
console.log(output)
I think you want to do it like this in case you want to avoid manually take a and b and other property except 'det' properties
function getResult(banana) {
const answer = [];
banana.forEach(element => {
const arrayData = element['det'];
delete element['det'];
// remove the 'del' property temporarily
arrayData.forEach(subElement => {
answer.push({
...element, // basically spread operator to make copy of all properties each time
...subElement
});
});
// restore the 'del' proprty
element['det'] = arrayData;
});
console.log("the result is : ", answer);
return answer;
}

How to fill in missing keys in an Array of Objects?

I have an Array of Objects which should all have the same keys, but some of the keys are missing. I would like to fill in the missing keys with a generic value.
I am looking for a simple way to do that (natively or via a library), the code below I use now works, bit looks to my untrained eyes quite heavy and I am sure I reinvented the tedious way to do something while there is a simple one.
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
]
// get the list of all keys
var allkeys = []
arr.forEach((objInArr) => {
allkeys = allkeys.concat(Object.keys(objInArr))
})
// check all arr entries for missing keys
arr.forEach((objInArr, i) => {
allkeys.forEach((key) => {
if (objInArr[key] === undefined) {
// the generic value, in this case 0
arr[i][key] = 0
}
})
})
console.log(arr)
Here is a version using property spread in object literals, although this will have very limited browser support:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
]
// Create an object with all the keys in it
// This will return one object containing all keys the items
let obj = arr.reduce((res, item) => ({...res, ...item}));
// Get those keys as an array
let keys = Object.keys(obj);
// Create an object with all keys set to the default value (0)
let def = keys.reduce((result, key) => {
result[key] = 0
return result;
}, {});
// Use object destrucuring to replace all default values with the ones we have
let result = arr.map((item) => ({...def, ...item}));
// Log result
console.log(result);
Your version is fine, although I would probably avoid all those array concat calls by just building up an object (or Set) with the keys. It's also a bit less clunky with for-of:
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
];
// Get all the keys
const keyObj = Object.create(null);
for (const entry of arr) {
for (const key of Object.keys(entry)) {
keyObj[key] = true;
}
}
const allkeys = Object.keys(keyObj);
// Check all arr entries for missing keys
for (const entry of arr) {
for (const key of allkeys) {
if (entry[key] === undefined) { // ***I'd change this
entry[key] = 0;
}
}
}
console.log(arr);
.as-console-wrapper {
max-height: 100% !important;
}
Re *** I'd change this: Note that there's a difference between a property that exists and has the value undefined and a property that doesn't exist at all. Your code is treating them as the same thing. Of course, if you know they won't have the value undefined (for instance, because of the API you're getting them from)...
You can use Object.assign to merge each element with an object holding default key-values:
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
];
var defaultObj = arr.reduce((m, o) => (Object.keys(o).forEach(key => m[key] = 0), m), {});
arr = arr.map(e => Object.assign({}, defaultObj, e));
console.log(arr);

To find object whereabouts in array of objects

function whatIsInAName(collection, source) {
var result = [];
var arr1 = Object.keys(source);
console.log(arr1);
for (var i = 0; i < collection.length; i++) {
for (var j = 0; j < arr1.length; j++) {
if (collection[i].hasOwnProperty(arr1[j]) === false) { //Check 1 if false go to next object in collection
break;
} else if (collection[i].hasOwnProperty(arr1[j])) {
console.log(source[arr1[j]], collection[i][arr1[j]])
if (source[arr1[j]] !== collection[i][arr1[j]]) { //Check 2 if value is not equal break loop and goto next object in collection
break;
}
continue; // if both check passes go for next property of source to check in object;
}
result.push(collection[i]); //if all values are present and checked in object push it in result array.
}
}
return result;
}
console.log(whatIsInAName(
[
{ a: 1, b: 2 },
{ a: 1 },
{ a: 1, b: 2, c: 2 }
], {
a: 1,
b: 2
}
));
I couldn't figure out the problem in my logic. I try to debug it even but can't find what the hell is a problem with logic.The program is to make a function that looks through an array of objects (first argument) and returns an array of all objects that have matching property and value pairs (second argument) Kindly help me over, please.
whatIsInAName([{ "a": 1, "b": 2 }, { "a": 1 }, { "a": 1, "b": 2, "c": 2 }], { "a": 1, "b": 2 })
should return
[{ "a": 1, "b": 2 }, { "a": 1, "b": 2, "c": 2 }].
and
whatIsInAName([{ "a": 1 }, { "a": 1 }, { "a": 1, "b": 2 }], { "a": 1 })
should return
[{ "a": 1 }, { "a": 1 }, { "a": 1, "b": 2 }].
Well you are complicating it too much with these two for loops, you can do it better using Array built-in methods.
This is how you can do it using .filter() and .some() methods:
function whatIsInAName(collection, source) {
var result = [];
var arr1 = Object.keys(source);
console.log(arr1);
result = collection.filter(function(obj){
return !arr1.some(function(k){
return !obj[k] || obj[k] !== source[k];
});
});
return result;
}
console.log(whatIsInAName([{ a: 1, b: 2 }, { a: 1 }, { a: 1, b: 2, c: 2 }], { a: 1, b: 2 }));
Problem with your code :
What you are doing currently is that if all the if statements pass, and loops do not break anywhere, you put continue again at the last item of arr1. So, it goes to check next iteration, does not find it, and goes to next iteration in the collection loop. While doing this, it does not push any item to result.
Solution:
In your code: You should use continue like this.
if(j !==(arr1.length - 1))
continue;
This gives your code an opportunity to push item to result array.
Try this function
function whatIsInAName(arr, sources){
return arr.filter((item) => {
for(source in sources){
if(!item[source] || item[source] !== sources[source]){
return false;
}
}
return true;
})
}
I think you are over complicating things. A simple .filter() will do. Inside that .filter() you can check to see if every value for every key in source matches with the corresponding key in collection by using the handy, built-in .every() method.
function whatIsInAName(collection, source) {
var sourceKeys = Object.keys(source);
return collection.filter(function (coll) {
// if you want to return only exact matches, just add the test to make sure same # of keys, and all keys match w/ values.
// (Object.keys(coll).length === sourceKeys.length) && sourceKeys.every(...)
return sourceKeys.every(function (key) {
return coll[key] === source[key];
});
});
}
console.log(whatIsInAName(
[
{ a: 1, b: 2 },
{ a: 1 },
{ a: 1, b: 2, c: 2 }
], {
a: 1,
b: 2
}
));
Alternatively, with ES6 Syntax:
function whatIsInAName(collection, source) {
return collection.filter(coll => Object.keys(source).every(key => coll[key] === source[key]));
}
console.log(whatIsInAName(
[
{ a: 1, b: 2 },
{ a: 1 },
{ a: 1, b: 2, c: 2 }
], {
a: 1,
b: 2
}
));
Your issue is that you are hitting your continue instead of falling out of the loop. I updated your code here:
function whatIsInAName(collection, source) {
var result = [];
var arr1 = Object.keys(source);
console.log(arr1);
for (var i = 0; i < collection.length; i++) {
for (var j = 0; j < arr1.length; j++) {
if (!collection[i].hasOwnProperty(arr1[j])) { //Check 1 if false go to next object in collection
break;
} else if (collection[i].hasOwnProperty(arr1[j])) {
console.log(source[arr1[j]], collection[i][arr1[j]])
if (source[arr1[j]] !== collection[i][arr1[j]]) { //Check 2 if value is not equal break loop and goto next object in collection
break;
}else if(j < arr1.length - 1){
continue; // if both check passes go for next property of source to check in object;
}
}
result.push(collection[i]);
}
}
return result;
}
console.log(whatIsInAName(
[
{ a: 1, b: 2 },
{ a: 1 },
{ a: 1, b: 2, c: 2 }
],
{
a: 1,
b: 2
}
)); // result is [Object {...}, Object {...}] which objects are {a: 1, b: 2}, {a: 1, b: 2, c: 2}

How to concatenate properties from multiple JavaScript objects

I am looking for the best way to "add" multiple JavaScript objects (associative arrays).
For example, given:
a = { "one" : 1, "two" : 2 };
b = { "three" : 3 };
c = { "four" : 4, "five" : 5 };
what is the best way to compute:
{ "one" : 1, "two" : 2, "three" : 3, "four" : 4, "five" : 5 }
ECMAscript 6 introduced Object.assign() to achieve this natively in Javascript.
The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
MDN documentation on Object.assign()
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
Object.assign is supported in many modern browsers but not yet all of them. Use a transpiler like Babel and Traceur to generate backwards-compatible ES5 JavaScript.
ECMAScript 6 has spread syntax. And now you can do this:
const obj1 = { 1: 11, 2: 22 };
const obj2 = { 3: 33, 4: 44 };
const obj3 = { ...obj1, ...obj2 };
console.log(obj3); // {1: 11, 2: 22, 3: 33, 4: 44}
This should do it:
function collect() {
var ret = {};
var len = arguments.length;
for (var i = 0; i < len; i++) {
for (p in arguments[i]) {
if (arguments[i].hasOwnProperty(p)) {
ret[p] = arguments[i][p];
}
}
}
return ret;
}
let a = { "one" : 1, "two" : 2 };
let b = { "three" : 3 };
let c = { "four" : 4, "five" : 5 };
let d = collect(a, b, c);
console.log(d);
Output:
{
"one": 1,
"two": 2,
"three": 3,
"four": 4,
"five": 5
}
You could use jquery's $.extend like this:
let a = { "one" : 1, "two" : 2 },
b = { "three" : 3 },
c = { "four" : 4, "five" : 5 };
let d = $.extend({}, a, b, c)
console.log(d)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Underscore has few methods to do this;
1. _.extend(destination, *sources)
Copy all of the properties in the source objects over to the destination object, and return the destination object.
_.extend(a, _.extend(b, c));
=> {"one" : 1, "two" : 2, "three" : 3, "four" : 4, "five" : 5 }
Or
_.extend(a, b);
=> {"one" : 1, "two" : 2, "three" : 3}
_.extend(a, c);
=> {"one" : 1, "two" : 2, "three" : 3, "four" : 4, "five" : 5 }
2. _.defaults(object, *defaults)
Fill in undefined properties in object with values from the defaults objects, and return the object.
_.defaults(a, _.defaults(b, c));
=> {"one" : 1, "two" : 2, "three" : 3, "four" : 4, "five" : 5 }
Or
_.defaults(a, b);
=> {"one" : 1, "two" : 2, "three" : 3}
_.defaults(a, c);
=> {"one" : 1, "two" : 2, "three" : 3, "four" : 4, "five" : 5 }
Shallow-cloning (excluding prototype) or merging of objects is now possible using a shorter syntax than Object.assign().
Spread syntax for object literals was introduced in ECMAScript 2018):
const a = { "one": 1, "two": 2 };
const b = { "three": 3 };
const c = { "four": 4, "five": 5 };
const result = {...a, ...b, ...c};
// Object { "one": 1, "two": 2 , "three": 3, "four": 4, "five": 5 }
Spread (...) operator is supported in many modern browsers but not all of them.
So, it is recommend to use a transpiler like Babel to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
This is the equivalent code Babel will generate for you:
"use strict";
var _extends = Object.assign || function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var a = { "one": 1, "two": 2 };
var b = { "three": 3 };
var c = { "four": 4, "five": 5 };
var result = _extends({}, a, b, c);
// Object { "one": 1, "two": 2 , "three": 3, "four": 4, "five": 5 }
Why should the function be restricted to 3 arguments? Also, check for hasOwnProperty.
function Collect() {
var o={};
for(var i=0;i<arguments.length;i++) {
var arg=arguments[i];
if(typeof arg != "object") continue;
for(var p in arg) {
if(arg.hasOwnProperty(p)) o[p] = arg[p];
}
}
return o;
}
It's easy using ES7 spread operator for an object, in your browser console put
({ name: "Alex", ...(true ? { age: 19 } : { })}) // {name: "Alex", age: 19}
({ name: "Alex", ...(false ? { age: 19 } : { })}) // {name: "Alex", }
To merge a dynamic number of objects, we can use Object.assign with spread syntax.
const mergeObjs = (...objs) => Object.assign({}, ...objs);
The above function accepts any number of objects, merging all of their properties into a new object with properties from later objects overwriting those from previous objects.
Demo:
const mergeObjs = (...objs) => Object.assign({}, ...objs);
const a = {prop: 1, prop2: '2'},
b = {prop3: 3, prop4: [1,2,3,4]}
c = {prop5: 5},
d = {prop6: true, prop7: -1},
e = {prop1: 2};
const abcd = mergeObjs(a,b,c,d);
console.log("Merged a,b,c,d:", abcd);
const abd = mergeObjs(a,b,d);
console.log("Merged a,b,d:", abd);
const ae = mergeObjs(a,e);//prop1 from e will overwrite prop1 from a
console.log("Merged a,e:", ae);
To merge an array of objects, a similar method may be applied.
const mergeArrayOfObjs = arr => Object.assign({}, ...arr);
Demo:
const mergeArrayOfObjs = arr => Object.assign({}, ...arr);
const arr = [
{a: 1, b: 2},
{c:1, d:3},
{abcd: [1,2,3,4], d: 4}
];
const merged = mergeArrayOfObjs(arr);
console.log(merged);
function Collect(a, b, c) {
for (property in b)
a[property] = b[property];
for (property in c)
a[property] = c[property];
return a;
}
Notice: Existing properties in previous objects will be overwritten.
ES6 ++
The question is adding various different objects into one.
let obj = {};
const obj1 = { foo: 'bar' };
const obj2 = { bar: 'foo' };
Object.assign(obj, obj1, obj2);
//output => {foo: 'bar', bar: 'foo'};
lets say you have one object with multiple keys that are objects:
let obj = {
foo: { bar: 'foo' },
bar: { foo: 'bar' }
}
this was the solution I found (still have to foreach :/)
let objAll = {};
Object.values(obj).forEach(o => {
objAll = {...objAll, ...o};
});
By doing this we can dynamically add ALL object keys into one.
// Output => { bar: 'foo', foo: 'bar' }
Probably, the fastest, efficient and more generic way is this (you can merge any number of objects and even copy to the first one ->assign):
function object_merge(){
for (var i=1; i<arguments.length; i++)
for (var a in arguments[i])
arguments[0][a] = arguments[i][a];
return arguments[0];
}
It also allows you to modify the first object as it passed by reference.
If you don't want this but want to have a completely new object containing all properties, then you can pass {} as the first argument.
var object1={a:1,b:2};
var object2={c:3,d:4};
var object3={d:5,e:6};
var combined_object=object_merge(object1,object2,object3);
combined_object and object1 both contain the properties of object1,object2,object3.
var object1={a:1,b:2};
var object2={c:3,d:4};
var object3={d:5,e:6};
var combined_object=object_merge({},object1,object2,object3);
In this case, the combined_object contains the properties of object1,object2,object3 but object1 is not modified.
Check here: https://jsfiddle.net/ppwovxey/1/
Note: JavaScript objects are passed by reference.
Simplest: spread operators
var obj1 = {a: 1}
var obj2 = {b: 2}
var concat = { ...obj1, ...obj2 } // { a: 1, b: 2 }
function collect(a, b, c){
var d = {};
for(p in a){
d[p] = a[p];
}
for(p in b){
d[p] = b[p];
}
for(p in c){
d[p] = c[p];
}
return d;
}

Categories