Merge json object and create new object, inside json object using javascript - javascript

My JSON looks like this
{"rows":[
{"shiftId":1,"shift":"Morning","item":"Tea","value":20},
{"shiftId":1,"shift":"Morning","item":"Coffee","value":30},
{"shiftId":2,"shift":"Evening","item":"Tea","value":40},
{"shiftId":2,"shift":"Evening","item":"Coffee","value":35}
]}
and I am looking to merge all the same shift and add the values of the merged keys together and create new Object for item to get something looking like this
{"rows":[
{
"shiftId":1,
"shift":"Morning",
"item":[{"itemName":"Tea"},{"itemName":"Coffee"}],
"value":50
},
{
"shiftId":2,
"shift":"Evening",
"item":[{"itemName":"Tea"},{"itemName":"Coffee"}],
"value":75
}
]}
I am try like this
var merged = {rows: []};
data.forEach(function (source) {
if (!merged.rows.some(function (row) {
return row.shiftId == source.shiftId;
})) {
merged.rows.push({
shiftId: source.shift,
shift: source.shift,
item: [{
itemName: source.shift
}],
value: source.value
});
} else {
var existRow = merged.rows.filter(function (existRow) {
return existRow.shiftId == source.shiftId
})[0];
existRow.total += source.total;
existRow.item = source.item.push(existRow.item);
}
});
But not working correctly. Thanks advance.

You could use a hash table as a reference to the objects with the same shiftId and return a new array with the collected and grouped data.
var data = { rows: [{ shiftId: 1, shift: "Morning", item: "Tea", value: 20 }, { shiftId: 1, shift: "Morning", item: "Coffee", value: 30 }, { shiftId: 2, shift: "Evening", item: "Tea", value: 40 }, { shiftId: 2, shift: "Evening", item: "Coffee", value: 35 }] },
result = {
rows: data.rows.reduce(function (hash) {
return function (r, a) {
if (!hash[a.shiftId]) {
hash[a.shiftId] = { shiftId: a.shiftId, shift: a.shift, item: [], value: 0 };
r.push(hash[a.shiftId]);
}
hash[a.shiftId].item.push({ itemName: a.item });
hash[a.shiftId].value += a.value;
return r;
};
}(Object.create(null)), [])
};
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Use a hash table:
var hash={};
var input={"rows":[
{"shiftId":1,"shift":"Morning","item":"Tea","value":20},
{"shiftId":1,"shift":"Morning","item":"Coffee","value":30},
{"shiftId":2,"shift":"Evening","item":"Tea","value":40},
{"shiftId":2,"shift":"Evening","item":"Coffee","value":35}
]}.rows;
input.forEach(function(row){
var el=(hash[row.shiftId]=hash[rows.shiftId]||{shiftId:row.shiftId,shift:row.shift,items:[],value:0});
el.items.push({itemName:row.itemName});
el.value+=row.value;
});
Now you can create your result like this:
var result={rows:[]};
for(key in hash){
result.rows.push(hash[key]);
}

Using Array.prototype.reduce()
const arr = [{
"shiftId": 1,
"shift": "Morning",
"item": "Tea",
"value": 20
},
{
"shiftId": 1,
"shift": "Morning",
"item": "Coffee",
"value": 30
},
{
"shiftId": 2,
"shift": "Evening",
"item": "Tea",
"value": 40
},
{
"shiftId": 2,
"shift": "Evening",
"item": "Coffee",
"value": 35
}
];
const newArr = arr.reduce((acc, item, i) => {
if (!acc.length) { // First time
acc.push(item)
return acc;
}
if (acc[acc.length - 1].shiftId === item.shiftId) { // if current shiftId === last shiftId
if (!(acc[acc.length - 1].item instanceof Array)) {
acc[acc.length - 1].item = [{ // Convert item to be an array
"itemName": acc[acc.length - 1].item
}]
}
acc[acc.length - 1].item.push({ // Add current item name to the last item array
"itemName": item.item
});
acc[acc.length - 1].value = acc[acc.length - 1].value + item.value // value addition
} else { // If new shiftId
acc.push(item);
}
return acc;
}, []);
console.log(newArr);

a = {"rows":[
{"shiftId":1,"shift":"Morning","item":"Tea","value":20},
{"shiftId":1,"shift":"Morning","item":"Coffee","value":30},
{"shiftId":2,"shift":"Evening","item":"Tea","value":40},
{"shiftId":2,"shift":"Evening","item":"Coffee","value":35}
]};
newa = {"rows":[]};
lastshiftid = -1;
newkey = -1;
for(key in a.rows){
curshiftid = a.rows[key].shiftId;
o = {"item":a.rows[key].item};
if(lastshiftid!=curshiftid){
newkey++;
a.rows[key].item = [];
newa.rows.push(a.rows[key]);
}
newa.rows[newkey].item.push(o);
lastshiftid = curshiftid;
}
console.log(newa.rows);
Fiddle: https://jsfiddle.net/t74ygy9L/

Related

Delete main array when nested array is empty

I got the following array:
var arr = [{
"mainId": 1,
"parents": [{
"parent": 1
},
{
"parent": 2
}
]
},
{
"mainId": 2,
"parents": [{
"parent": 3
}]
}
]
I'm using this function to delete an specific parent in the parents array
var idToDelete = 2
arr.forEach(function (o) {
o.parents = o.parents.filter(s => s.id !== idToDelete
})
This is working fine. But when idToDelete = 3 I want to delete the complete main Item and start a function "startSomething"
So that I'm left with the following output
var arr = [{
"mainId": 1,
"parents": [{
"parent": 1
},
{
"parent": 2
}
]
}]
How could this be implemented?
You can map and then filter the top level array, here is an example:
var arr = [{
"mainId": 1,
"parents": [{
"parent": 1
},
{
"parent": 2
}
]
},
{
"mainId": 2,
"parents": [{
"parent": 3
}]
}
];
var idToDelete = 3;
arr = arr.map((v) => ({
...v,
parents: v.parents.filter(({
parent
}) => parent !== idToDelete)
}))
.filter((v) => v.parents.length);
console.log(arr);
Filter the parent array by whether it has a .length after mutating the children. Also make sure to use the .parent property (your objects have no .id property):
const startSomething = () => console.log('startSomething');
var arr =
[
{
"mainId": 1,
"parents": [
{
"parent": 1
},
{
"parent": 2
}
]
},
{
"mainId": 2,
"parents": [
{
"parent": 3
}
]
}
];
var idToDelete = 3;
arr.forEach(function (o) {
o.parents = o.parents.filter(s => s.parent !== idToDelete)
});
const newArr = arr.filter(({ parents }) => parents.length);
console.log(newArr);
if (newArr.length !== arr.length) {
startSomething();
}
You can use .filter() on the array itself (to remove the main object) with .some() to return true or false depending on whether it should be deleted or not:
const arr = [{ "mainId": 1, "parents": [{ "parent": 1 }, { "parent": 2 } ] }, { "mainId": 2, "parents": [{ "parent": 3 }] } ];
const removeObj = (arr, id, cb) => {
const res = arr.filter(({parents}) => !parents.some(({parent}) => parent === id));
const item_removed = res.length !== arr.length;
return (item_removed && cb(res), item_removed);
}
const startSomething = new_arr => console.log("Starting with arr", new_arr);
removeObj(arr, 3, startSomething);
.as-console-wrapper { max-height: 100% !important;} /* ignore */
Here's a alternative method, with a function that uses findIndex to find the object in the array.
Then splices the object if it only has 1 parent.
Or splices the parent from object if it has more than 1 parent.
Example snippet:
const deleteParent = (arr, id) =>
{
let idx = arr.findIndex(x=>x.parents.some(p=> p.parent === id));
if (idx >= 0 && arr[idx].parents.length === 1 ) {
let obj = arr[idx];
// remove object from array and do something
arr.splice(idx, 1);
doSomething(obj);
}
else if(idx >= 0){
let obj = arr[idx];
let parentIdx = obj.parents.findIndex(x=>x.parent==id);
// remove parent
obj.parents.splice( parentIdx, 1);
}
}
function doSomething (obj) { console.log(`Deleted mainId ${obj.mainId}`) }
var arr = [
{
"mainId": 1,
"parents": [
{
"parent": 1
},
{
"parent": 2
}
]
},
{
"mainId": 2,
"parents": [
{
"parent": 3
}
]
}
];
deleteParent(arr, 3);
console.log(JSON.stringify(arr));
deleteParent(arr, 2);
console.log(JSON.stringify(arr));

How to reduce an array while merging one of it's field as well

I have this,
var o = [{
"id": 1, // its actually a string in real life
"course": "name1",
// more properties
},
{
"id": 1, // its actually a string in real life
"course": "name2",
// more properties
}];
I want this,
var r = [{
"id": 1, // its actually a string in real life
"course": ["name1", "name2"],
}];
I am trying this,
var flattened = [];
for (var i = 0; i < a.length; ++i) {
var current = a[i];
if(flattened.)
}
but I am stuck, I am not sure what to do next, array will have more then 2 records but this was just an example.
THERE are more fields but I removed them for simplicity, I won't be using them in final array.
You could reduce the array and find the object.
var array = [{ id: 1, course: "name1" }, { id: 1, course: "name2" }],
flat = array.reduce((r, { id, course }) => {
var temp = r.find(o => id === o.id);
if (!temp) {
r.push(temp = { id, course: [] });
}
temp.course.push(course);
return r;
}, []);
console.log(flat);
The same by taking a Map.
var array = [{ id: 1, course: "name1" }, { id: 1, course: "name2" }],
flat = Array.from(
array.reduce((m, { id, course }) => m.set(id, [...(m.get(id) || []) , course]), new Map),
([id, course]) => ({ id, course })
);
console.log(flat);
This way you will get the data flattened in the shape you want
const o = [
{
id: 1,
course: "name1"
},
{
id: 1,
course: "name2"
},
{
id: 2,
course: "name2"
}
];
const r = o.reduce((acc, current) => {
const index = acc.findIndex(x => x.id === current.id);
if (index !== -1) {
acc[index].course.push(current.course);
} else {
acc.push({id:current.id, course: [current.course]});
}
return acc
}, []);
console.log(r);
You can do this with reduce and Object.entries. This example works for any number of properties:
const o = [
{ id: 1, course: 'name1', time: 'morning', topic: 'math' },
{ id: 1, course: 'name2', time: 'afternoon' },
{ id: 2, course: 'name3', time: 'evening' }
];
const result = o.reduce((out, { id, ...rest }) => {
out[id] = out[id] || {};
const mergedProps = Object.entries(rest).reduce((acc, [k, v]) => {
return { ...acc, [k]: [...(out[id][k] || []), v] };
}, out[id]);
out[id] = { id, ...mergedProps };
return out;
}, {});
console.log(result);
If you only care about the id and course fields, you can simplify to this:
const o = [
{ id: 1, course: 'name1', time: 'morning', topic: 'math' },
{ id: 1, course: 'name2', time: 'afternoon' },
{ id: 2, course: 'name3', time: 'evening' }
];
const result = o.reduce((out, { id, course }) =>
({ ...out, [id]: { id, course: [...((out[id] || {}).course || []), course] } })
, {});
console.log(result);
You could use .reduce to create an object of keys, and then use that object to set keys to be of the id. This way you can add to the same course array by targetting the id of the object. Lastly, you can get the values of the object to get your result.
See example below:
var o = [{
"id": 1,
"course": "name1",
"foo": 1
},
{
"id": 1,
"course": "name2",
"bar": 2
}];
var res = Object.values(o.reduce((acc, {id, course, ...rest}) => {
if(id in acc)
acc[id] = {...acc[id], course: [...acc[id].course, course], ...rest};
else acc[id] = {id, course: [course], ...rest};
return acc;
}, {}));
console.log(res);
function merge(array, key = 'id') {
const obj = {}
for(const item of array) {
const existing = obj[item[key]]
if(existing) {
for(const [name, value] of Object.entries(item)) {
if(name === key) continue;
if(existing[name]) {
existing[name] = [ ...(existing[name].$custom ? existing[name] : [existing[name]]), value ]
existing[name].$custom = true;
} else {
existing[name] = value;
}
}
} else {
obj[item[key]] = { ...item }
}
}
return Object.values(obj)
}
var o = [
{
"id": 1,
"single": "test"
},
{
"id": 1,
"course": "name1",
"multifield": "test"
},
{
"id": 1,
"course": "name2"
},
{
"id": 1,
"newfield": "test"
}, {
"id": 2,
"anotherid": "test",
"array": [1,3,4]
}, {
"id": 2,
"array": "text"
}];
console.log(merge(o))
You can use reduce to accumulate the results. Search in the current result (accumulator a) for an object (el) with the same id, if found, append course to existing object and return the same accumulator, otherwise put into the accumulator with course as an array.
var res = o.reduce((a, {id,course}) => {
var found = a.find(el => el.id == id);
return found ? found.course.push(course) && a : [...a, {id, course: [course]}];
}, []);

Iterate through an array of objects, to find objects that contain 2 values

I currently have an array that looks like this:
var array = [
{
"name": "a",
"age": 1,
"siblings": 3
},
{
"name": "b",
"age": 3,
"siblings": 5
},
{
"name": "a",
"age": 1,
"siblings": 2
}
]
I want to create a function that takes 2 values, and returns a new array containing the objects that match those two values.
var name = "a";
var age = 1;
someFunction(name, age);
And returns something that looks like this:
newArray = [
{
"name": "a",
"age": 1,
"siblings": 3
},
{
"name": "a",
"age": 1,
"siblings": 2
}
]
I have tried using the filter method and the reduce methods but no success. If someone could help me or point me in the right direction, I would greatly appreciate that.
var array = [
{
"name": "a",
"age": 1,
"siblings": 3
},
{
"name": "b",
"age": 3,
"siblings": 5
},
{
"name": "a",
"age": 1,
"siblings": 2
}
]
var result = array.filter(function(obj) {
return obj.name === "a" && obj.age === 1;
});
return result[0];
}
console.log(result);
You could take an array with the wanted key/value pair for searching.
Take Array#filter with a check for the predicates with Array#every.
var array = [{ name: "a", age: 1, siblings: 3 }, { name: "b", age: 3, siblings: 5 }, { name: "a", age: 1, siblings: 2 }],
search = [['name', 'a'], ['age', 1]],
result = array.filter(object => search.every(([key, value]) => object[key] === value));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
here is your working function
function someFunction (name, age) {
return array.filter(val => val.name == name && val.age == age)
}
In the Array#filter callback, just compare the name & age of current element with those of the params you passed.
var array = [{"name":"a","age":1,"siblings":3},{"name":"b","age":3,"siblings":5},{"name":"a","age":1,"siblings":2}]
var name = "a";
var age = 1;
function someFunction(name, age){
return array.filter((obj) => obj.name === name && obj.age === age)
}
console.log(someFunction(name, age));
You can also use Array#reduce. Just add the matching object in the accumulator array and return it.
var array = [{"name":"a","age":1,"siblings":3},{"name":"b","age":3,"siblings":5},{"name":"a","age":1,"siblings":2}]
var name = "a";
var age = 1;
function someFunction(name, age){
return array.reduce((acc, obj) => {
if(obj.name === name && obj.age === age)
acc.push(obj);
return acc;
}, []);
}
console.log(someFunction(name, age));
Use the filter function
var array = [
{
"name": "a",
"age": 1,
"siblings": 3
},
{
"name": "b",
"age": 3,
"siblings": 5
},
{
"name": "a",
"age": 1,
"siblings": 2
}
]
var name = "a";
var age = 1;
someFunction(name, age);
function someFunction(name,age)
{
console.log(array.filter((e)=>e.name==name && e.age==age?true:false
))}
Try this
let array = [
{
"name": "a",
"age": 1,
"siblings": 3
},
{
"name": "b",
"age": 3,
"siblings": 5
},
{
"name": "a",
"age": 1,
"siblings": 2
}
];
function someFunction(name, age) {
let newArray = [];
array.forEach(function(arr) {
let nameMatch = false;
let ageMatch = false;
let obj = arr;
for(key in obj) {
if(obj.hasOwnProperty(key)) {
if(key === 'name') {
// console.log('name key i am', key);
let nameValue = obj[key];
if(nameValue === name) {
nameMatch = true;
// console.log('name match', nameValue, name);
}
} else if(key === 'age') {
let ageValue = obj[key];
if(ageValue === age) {
// console.log('age match', ageValue, age);
ageMatch = true
}
}
}
}
if(ageMatch && nameMatch) {
// console.log('true');
newArray.push(arr);
// console.log(arr);
}
});
return newArray;
}
let name = "a";
let age = 1;
let myArr = someFunction(name, age);
console.log('My Arr', myArr);

Merge two array of objects based on a key

I have two arrays:
Array 1:
[
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4052", date: "2017-01-22" }
]
and array 2:
[
{ id: "abdc4051", name: "ab" },
{ id: "abdc4052", name: "abc" }
]
I need to merge these two arrays based on id and get this:
[
{ id: "abdc4051", date: "2017-01-24", name: "ab" },
{ id: "abdc4052", date: "2017-01-22", name: "abc" }
]
How can I do this without iterating trough Object.keys?
You can do it like this -
let arr1 = [
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4052", date: "2017-01-22" }
];
let arr2 = [
{ id: "abdc4051", name: "ab" },
{ id: "abdc4052", name: "abc" }
];
let arr3 = arr1.map((item, i) => Object.assign({}, item, arr2[i]));
console.log(arr3);
Use below code if arr1 and arr2 are in a different order:
let arr1 = [
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4052", date: "2017-01-22" }
];
let arr2 = [
{ id: "abdc4051", name: "ab" },
{ id: "abdc4052", name: "abc" }
];
let merged = [];
for(let i=0; i<arr1.length; i++) {
merged.push({
...arr1[i],
...(arr2.find((itmInner) => itmInner.id === arr1[i].id))}
);
}
console.log(merged);
Use this if arr1 and arr2 are in a same order
let arr1 = [
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4052", date: "2017-01-22" }
];
let arr2 = [
{ id: "abdc4051", name: "ab" },
{ id: "abdc4052", name: "abc" }
];
let merged = [];
for(let i=0; i<arr1.length; i++) {
merged.push({
...arr1[i],
...arr2[i]
});
}
console.log(merged);
You can do this in one line
let arr1 = [
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4052", date: "2017-01-22" }
];
let arr2 = [
{ id: "abdc4051", name: "ab" },
{ id: "abdc4052", name: "abc" }
];
const mergeById = (a1, a2) =>
a1.map(itm => ({
...a2.find((item) => (item.id === itm.id) && item),
...itm
}));
console.log(mergeById(arr1, arr2));
Map over array1
Search through array2 for array1.id
If you find it ...spread the result of array2 into array1
The final array will only contain id's that match from both arrays
This solution is applicable even when the merged arrays have different sizes.
Also, even if the matching keys have different names.
Merge the two arrays by using a Map as follows:
const arr1 = [
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4052", date: "2017-01-22" },
{ id: "abdc4053", date: "2017-01-22" }
];
const arr2 = [
{ nameId: "abdc4051", name: "ab" },
{ nameId: "abdc4052", name: "abc" }
];
const map = new Map();
arr1.forEach(item => map.set(item.id, item));
arr2.forEach(item => map.set(item.nameId, {...map.get(item.nameId), ...item}));
const mergedArr = Array.from(map.values());
console.log(JSON.stringify(mergedArr));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Run the stack snippet to see the result:
[
{
"id": "abdc4051",
"date": "2017-01-24",
"nameId": "abdc4051",
"name": "ab"
},
{
"id": "abdc4052",
"date": "2017-01-22",
"nameId": "abdc4052",
"name": "abc"
},
{
"id": "abdc4053",
"date": "2017-01-22"
}
]
Here's an O(n) solution using reduce and Object.assign
const joinById = ( ...lists ) =>
Object.values(
lists.reduce(
( idx, list ) => {
list.forEach( ( record ) => {
if( idx[ record.id ] )
idx[ record.id ] = Object.assign( idx[ record.id ], record)
else
idx[ record.id ] = record
} )
return idx
},
{}
)
)
To use this function for the OP's case, pass in the arrays you want to join to joinById (notice lists is a rest parameter).
let joined = joinById(list1, list2)
Each list gets reduced to a single object where the keys are ids and the values are the objects. If there's a value at the given key already, it gets object.assign called on it and the current record.
Here's the generic O(n*m) solution, where n is the number of records and m is the number of keys. This will only work for valid object keys. You can convert any value to base64 and use that if you need to.
const join = ( keys, ...lists ) =>
lists.reduce(
( res, list ) => {
list.forEach( ( record ) => {
let hasNode = keys.reduce(
( idx, key ) => idx && idx[ record[ key ] ],
res[ 0 ].tree
)
if( hasNode ) {
const i = hasNode.i
Object.assign( res[ i ].value, record )
res[ i ].found++
} else {
let node = keys.reduce( ( idx, key ) => {
if( idx[ record[ key ] ] )
return idx[ record[ key ] ]
else
idx[ record[ key ] ] = {}
return idx[ record[ key ] ]
}, res[ 0 ].tree )
node.i = res[ 0 ].i++
res[ node.i ] = {
found: 1,
value: record
}
}
} )
return res
},
[ { i: 1, tree: {} } ]
)
.slice( 1 )
.filter( node => node.found === lists.length )
.map( n => n.value )
This is essentially the same as the joinById method, except that it keeps an index object to identify records to join. The records are stored in an array and the index stores the position of the record for the given key set and the number of lists it's been found in.
Each time the same key set is encountered, it finds the node in the tree, updates the element at it's index, and the number of times it's been found is incremented.
After joining, the idx object is removed from the array with the slice and any elements that weren't found in each set are removed. This makes it an inner join, you could remove this filter and have a full outer join.
Finally each element is mapped to it's value, and you have the joined arrays.
You could use an arbitrary count of arrays and map on the same index new objects.
var array1 = [{ id: "abdc4051", date: "2017-01-24" }, { id: "abdc4052", date: "2017-01-22" }],
array2 = [{ id: "abdc4051", name: "ab" }, { id: "abdc4052", name: "abc" }],
result = [array1, array2].reduce((a, b) => a.map((c, i) => Object.assign({}, c, b[i])));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you have 2 arrays need to be merged based on values even its in different order
let arr1 = [
{ id:"1", value:"this", other: "that" },
{ id:"2", value:"this", other: "that" }
];
let arr2 = [
{ id:"2", key:"val2"},
{ id:"1", key:"val1"}
];
you can do like this
const result = arr1.map(item => {
const obj = arr2.find(o => o.id === item.id);
return { ...item, ...obj };
});
console.log(result);
To merge the two arrays on id, assuming the arrays are equal length:
arr1.map(item => ({
...item,
...arr2.find(({ id }) => id === item.id),
}));
We can use lodash here. _.merge works as you expected. It works with the common key present.
_.merge(array1, array2)
Non of these solutions worked for my case:
missing objects can exist in either array
runtime complexity of O(n)
notes:
I used lodash but it's easy to replace with something else
Also used Typescript (just remove/ignore the types)
import { keyBy, values } from 'lodash';
interface IStringTMap<T> {
[key: string]: T;
}
type IIdentified = {
id?: string | number;
};
export function mergeArrayById<T extends IIdentified>(
array1: T[],
array2: T[]
): T[] {
const mergedObjectMap: IStringTMap<T> = keyBy(array1, 'id');
const finalArray: T[] = [];
for (const object of array2) {
if (object.id && mergedObjectMap[object.id]) {
mergedObjectMap[object.id] = {
...mergedObjectMap[object.id],
...object,
};
} else {
finalArray.push(object);
}
}
values(mergedObjectMap).forEach(object => {
finalArray.push(object);
});
return finalArray;
}
You can use array methods
let arrayA=[
{id: "abdc4051", date: "2017-01-24"},
{id: "abdc4052", date: "2017-01-22"}]
let arrayB=[
{id: "abdc4051", name: "ab"},
{id: "abdc4052", name: "abc"}]
let arrayC = [];
arrayA.forEach(function(element){
arrayC.push({
id:element.id,
date:element.date,
name:(arrayB.find(e=>e.id===element.id)).name
});
});
console.log(arrayC);
//0:{id: "abdc4051", date: "2017-01-24", name: "ab"}
//1:{id: "abdc4052", date: "2017-01-22", name: "abc"}
Here is one-liner (order of elements in array is not important and assuming there is 1 to 1 relationship):
var newArray = array1.map(x=>Object.assign(x, array2.find(y=>y.id==x.id)))
I iterated through the first array and used the .find method on the second array to find a match where the id are equal and returned the result.
const a = [{ id: "abdc4051", date: "2017-01-24" },{ id: "abdc4052", date: "2017-01-22" }];
const b = [{ id: "abdc4051", name: "ab" },{ id: "abdc4052", name: "abc" }];
console.log(a.map(itm => ({...itm, ...b.find(elm => elm.id == itm.id)})));
You can recursively merge them into one as follows:
function mergeRecursive(obj1, obj2) {
for (var p in obj2) {
try {
// Property in destination object set; update its value.
if (obj2[p].constructor == Object) {
obj1[p] = this.mergeRecursive(obj1[p], obj2[p]);
} else {
obj1[p] = obj2[p];
}
} catch (e) {
obj1[p] = obj2[p];
}
}
return obj1;
}
arr1 = [
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4052", date: "2017-01-22" }
];
arr2 = [
{ id: "abdc4051", name: "ab" },
{ id: "abdc4052", name: "abc" }
];
mergeRecursive(arr1, arr2)
console.log(JSON.stringify(arr1))
Irrespective of the order you can merge it by,
function merge(array,key){
let map = {};
array.forEach(val=>{
if(map[val[key]]){
map[val[key]] = {...map[val[key]],...val};
}else{
map[val[key]] = val;
}
})
return Object.keys(map).map(val=>map[val]);
}
let b = [
{ id: "abdc4051", name: "ab" },
{ id: "abdc4052", name: "abc" }
];
let a = [
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4052", date: "2017-01-22" }
];
console.log(merge( [...a,...b], 'id'));
An approach if both two arrays have non-intersect items.
const firstArray = [
{ id: 1, name: "Alex", salutation: "Mr." },
{ id: 2, name: "Maria", salutation: "Ms." },
];
const secondArray = [
{ id: 2, address: "Larch Retreat 31", postcode: "123452" },
{ id: 3, address: "Lycroft Close 12D", postcode: "123009" },
];
const mergeArr = (arr1, arr2) => {
const obj = {};
arr1.forEach(item => {
obj[item.id] = item;
});
arr2.forEach(item => {
obj[item.id]
? (obj[item.id] = { ...obj[item.id], ...item })
: (obj[item.id] = item);
});
return Object.values(obj);
};
const output = mergeArr(firstArray, secondArray);
console.log(output);
Python 3 Solution for someone who lands on this page in hope of finding one
def merge(studentDetails, studentMark, merge_key):
student_details = {}
student_marks = {}
for sd, sm in zip(studentDetails, studentMark):
key = sd.pop(merge_key)
student_details[key] = sd
key = sm.pop(merge_key)
student_marks[key] = sm
res = []
for id, val in student_details.items():
# Merge three dictionary together
temp = {**{"studentId": id}, **val, **student_marks[id]}
res.append(temp)
return res
if __name__ == '__main__':
# Test Case 1
studentDetails = [
{"studentId": 1, "studentName": 'Sathish', "gender": 'Male', "age": 15},
{"studentId": 2, "studentName": 'kumar', "gender": 'Male', "age": 16},
{"studentId": 3, "studentName": 'Roja', "gender": 'Female', "age": 15},
{"studentId": 4, "studentName": 'Nayanthara', "gender": 'Female', "age": 16},
]
studentMark = [
{"studentId": 1, "mark1": 80, "mark2": 90, "mark3": 100},
{"studentId": 2, "mark1": 80, "mark2": 90, "mark3": 100},
{"studentId": 3, "mark1": 80, "mark2": 90, "mark3": 100},
{"studentId": 4, "mark1": 80, "mark2": 90, "mark3": 100},
]
# Test Case 2
array1 = [
{"id": "abdc4051", "date": "2017-01-24"},
{"id": "abdc4052", "date": "2017-01-22"}
]
array2 = [
{"id": "abdc4051", "name": "ab"},
{"id": "abdc4052", "name": "abc"}
]
output = merge(studentDetails, studentMark, merge_key="studentId")
[print(a) for a in output]
output = merge(array1, array2, merge_key="id")
[print(a) for a in output]
Output
{'studentId': 1, 'studentName': 'Sathish', 'gender': 'Male', 'age': 15, 'mark1': 80, 'mark2': 90, 'mark3': 100}
{'studentId': 2, 'studentName': 'kumar', 'gender': 'Male', 'age': 16, 'mark1': 80, 'mark2': 90, 'mark3': 100}
{'studentId': 3, 'studentName': 'Roja', 'gender': 'Female', 'age': 15, 'mark1': 80, 'mark2': 90, 'mark3': 100}
{'studentId': 4, 'studentName': 'Nayanthara', 'gender': 'Female', 'age': 16, 'mark1': 80, 'mark2': 90, 'mark3': 100}
{'studentId': 'abdc4051', 'date': '2017-01-24', 'name': 'ab'}
{'studentId': 'abdc4052', 'date': '2017-01-22', 'name': 'abc'}
Well... assuming both arrays are of the same length, I would probably do something like this:
var newArr = []
for (var i = 0; i < array1.length; i++ {
if (array1[i].id === array2[i].id) {
newArr.push({id: array1[i].id, date: array1[i].date, name: array2[i].name});
}
}
I was able to achieve this with a nested mapping of the two arrays and updating the initial array:
member.map(mem => {
return memberInfo.map(info => {
if (info.id === mem.userId) {
mem.date = info.date;
return mem;
}
}
}
There are a lot of solutions available for this, But, We can simply use for loop and if conditions to get merged arrays.
const firstArray = [
{ id: 1, name: "Alex", salutation: "Mr." },
{ id: 2, name: "Maria", salutation: "Ms." },
];
const secondArray = [
{ id: 1, address: "Larch Retreat 31", postcode: "123452" },
{ id: 2, address: "Lycroft Close 12D", postcode: "123009" },
];
let mergedArray: any = [];
for (const arr1 of firstArray) {
for (arr2 doc of secondArray) {
if (arr1.id === arr2.id) {
mergedArray.push({ ...arr1, ...arr2 });
}
}
}
console.log(mergedArray)
Here is converting the best answer (jsbisht) into a function that accepts the keys as arguments.
const mergeArraysByKeyMatch = (array1, array2, key1, key2) => {
const map = new Map();
array1.forEach((item) => map.set(item[key1], item));
array2.forEach((item) =>
map.set(item[key2], { ...map.get(item[key2]), ...item })
);
const merged = Array.from(map.values());
return merged;
};
A Typescript O(n+m) (which could be classified as O(n)) solution; without lodash:
// RequireAtLeastOne from https://stackoverflow.com/questions/40510611/typescript-interface-require-one-of-two-properties-to-exist/49725198#49725198
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
T,
Exclude<keyof T, Keys>
> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
}[Keys];
export const mergeDualArraysOnKey = <
K extends PropertyKey,
T extends RequireAtLeastOne<{ [f in PropertyKey]?: unknown }, K>
>(
key: K,
...lists: [T[], T[]]
): T[] => {
const lookup: { [key in string]: number } = {};
return lists[0].concat(lists[1]).reduce((acc: T[], value: T, i: number) => {
const lookupKey = `${value[key]}`;
if (lookup.hasOwnProperty(lookupKey)) {
acc[lookup[lookupKey]] = Object.assign({}, acc[lookup[lookupKey]], value);
} else {
acc.push(value);
lookup[lookupKey] = acc.length - 1;
}
return acc;
}, []);
};
First concatenates the two arrays and then iterates through the newly created array. It uses a lookup table (object) to store the index of an item in the final merged array which has the same key and merges the objects inplace.
If this needed to be extended to handle more arrays, could use a loop or recursion as a wrapping function:
const mergeArrays = <
K extends PropertyKey,
T extends RequireAtLeastOne<{ [f in PropertyKey]?: unknown }, K>
>(
key: K,
...lists: T[][]
): T[] => {
if (lists.length === 1) {
return lists[0];
}
const l1 = lists.pop() || [];
const l2 = lists.pop() || [];
return mergeArrays(key, mergeDualArraysOnKey(key, l1, l2), ...lists);
};
with usage being:
const arr1 = [
{ id: "abdc4052", date: "2017-01-22" },
{ id: "abdc4052", location: "US" },
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4053", date: "2017-01-24" },
{ id: "abdc4054", date: "2017-01-24" },
{ id: "abdc4055", location: "US" },
];
const arr2 = [
{ id: "abdc4052", date: "2017-01-22" },
{ id: "abdc4052", name: "abc" },
{ id: "abdc4055", date: "2017-01-24" },
{ id: "abdc4055", date: "2017-01-24", name: "abcd" },
];
const arr3 = [{ id: "abdc4056", location: "US" }];
const arr4 = [
{ id: "abdc4056", name: "abcde" },
{ id: "abdc4051", name: "ab--ab" },
];
mergeArrays<
"id",
{
id: string;
date?: string;
location?: string;
name?: string;
}
>("id", arr1, arr2, arr3, arr4)
Base on your example, you can do it this way:
const arrayOne = [
{ id: "abdc4051", date: "2017-01-24" },
{ id: "abdc4052", date: "2017-01-22" }
]
const arrayTwo = [
{ id: "abdc4051", name: "ab" },
{ id: "abdc4052", name: "abc" }
]
const mergeArrays = () => {
arrayOne.forEach((item, i) => {
const matchedFound = arrayTwo.findIndex(a => a.id === item.id);
arrayOne[i] = {
...item,
...matchedFound,
}
});
};
mergeArrays();
console.log(arrayOne);
This is a version when you have an object and an array and you want to merge them and give the array a key value so it fits into the object nicely.
var fileData = [
{ "id" : "1", "filename" : "myfile1", "score" : 33.1 },
{ "id" : "2", "filename" : "myfile2", "score" : 31.4 },
{ "id" : "3", "filename" : "myfile3", "score" : 36.3 },
{ "id" : "4", "filename" : "myfile4", "score" : 23.9 }
];
var fileQuality = [0.23456543,0.13413131,0.1941344,0.7854522];
var newOjbect = fileData.map((item, i) => Object.assign({}, item, {fileQuality:fileQuality[i]}));
console.log(newOjbect);

Merge JavaScript objects with the same key value and count them

I'm trying to merge objects with the same key value into one and count them. Is it even possible?
var array = {
"items": [{
"value": 10,
"id": "111",
"name": "BlackCat",
}, {
"value": 10,
"id": "111",
"name": "BlackCat",
}, {
"value": 15,
"id": "777",
"name": "WhiteCat",
}]
}
Desired output:
var finalArray = {
"items": [{
"value": 10,
"id": "111",
"name": "BlackCat",
"count": 2,
}, {
"value": 15,
"id": "777",
"name": "WhiteCat",
"count": 1,
}]
}
You can use reduce on your items array:
var combinedItems = array.items.reduce(function(arr, item) {
var found = false;
for (var i = 0; i < arr.length; i++) {
if (arr[i].id === item.id) {
found = true;
arr[i].count++;
}
}
if (!found) {
item.count = 1;
arr.push(item);
}
return arr;
}, [])
Fiddle: https://jsfiddle.net/6wqw79pn/
You could use a for loop:
var finalArray = [];
for (var i in array.items) {
var item = array.items[i];
var existingItem = finalArray.find(function(element){
return (element.id == item.id);
});
if (!existingItem) {
existingItem = {};
finalArray.push(existingItem);
} else {
if (!existingItem.count)
existingItem.count = 2;
else
existingItem.count++;
}
for (var j in item) {
existingItem[j] = item[j];
}
}
Working example: https://jsfiddle.net/mspinks/5kowbq4j/3/
Basically you could use a hash table with the value of id as key and count.
var object = { items: [{ value: 10, id: "111", name: "BlackCat", }, { value: 10, id: "111", name: "BlackCat", }, { value: 15, id: "777", name: "WhiteCat", }] },
result = object.items.reduce(function (hash) {
return function (r, o) {
if (!hash[o.id]) {
hash[o.id] = { value: o.value, id: o.id, name: o.name, count: 0 };
r.push(hash[o.id]);
}
hash[o.id].count++;
return r;
};
}(Object.create(null)), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Underscore js will be able to solve your problem very easily
var groups = _.groupBy(array.items, function(item){
return item.value + '#' + item.id + '#' + item.name;
});
var data = {'items' : []};
_.each(groups,function(group){
data.items.push({
'value' : group[0].value,
'id' : group[0].id,
'name' : group[0].name,
'count' : group.length
})
})
console.log(data)

Categories