Deep merging nested arrays - javascript

I have to merge 2 arrays with key value as follows:
array1 = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"test2"},{id:"yz", data:"test3"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"}]}]
with
array2 = [
{id:"456", data:[{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}]
to produce something like
merged = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"},{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}]
I've been trying this out for quite some time and can't get a solution that meets the scenario. Most of the solutions just do blind merging, not based on the id value. Tried using lodash mergeWith but didn't get the output needed. A Ramda solution is also acceptable.
Thanks,

This links could be helpful to you merge two arrays.
In this code snippet, i have tried to find the common objects between set1 and set2,if there are any i'm finding the unique properties and changing their content and also non existant properties in object2 and pushing it to object1
Check the following snippet.
var arr1 = [{
id: "123",
data: [{
id: "234",
data: "hello"
}, {
id: "345",
data: "there"
}, {
id: "xyz",
data: "yo"
}]
}, {
id: "456",
data: [{
id: "34",
data: "test"
}, {
id: "45",
data: "test2"
}, {
id: "yz",
data: "test3"
}]
}, {
id: "789",
data: [{
id: "23",
data: "aaa"
}, {
id: "34",
data: "bbb"
}, {
id: "xy",
data: "ccc"
}]
}]
var arr2 = [{
id: "456",
data: [{
id: "45",
data: "changed"
}, {
id: "yz",
data: "data"
}]
}, {
id: "789",
data: [{
id: "456",
data: "appended data"
}]
}, {
id: "890",
data: [{
id: "456",
data: "new data"
}]
}]
var arr3 = [];
for (var i in arr1) {
var shared = false;
for (var j in arr2)
if (arr2[j].id == arr1[i].id) {
shared = true;
// arr1[i].data.concat(arr2[j].data);
var set1 = pushproperties(arr1[i].data, arr2[j].data);
arr1[i].data = set1;
arr3.push(arr1[i]);
break;
}
if (!shared) {
arr3.push(arr1[i]);
arr3.push(arr2[j]);
}
}
function pushproperties(set1, set2) {
var filtered = false;
set2.forEach(function(item) {
filtered = set1.every(function(element) {
return element.id != item.id;
});
if (filtered) {
set1.push(item);
}
});
set1.forEach(function(item) {
set2.forEach(function(element) {
if (item.id == element.id) {
item.data = element.data;
}
});
});
return set1;
}
console.log(arr3);
Hope this helps

This a function the merges 2 arrays recursively using Array.prototype.reduce(). If it encounters items with the same id, and they have a data prop, which is an array, it merges them using the logic. If data is not an array, it's overridden by the last item instead.
function mergeArraysDeep(arr1, arr2) {
var unique = arr1.concat(arr2).reduce(function(hash, item) {
var current = hash[item.id];
if(!current) {
hash[item.id] = item;
} else if (Array.isArray(current.data)) {
current.data = mergeArraysDeep(current.data, item.data);
} else {
current.data = item.data;
}
return hash;
}, {});
return Object.keys(unique).map(function(key) {
return unique[key];
});
}
var array1 = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"test2"},{id:"yz", data:"test3"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"}]}
];
var array2 = [
{id:"456", data:[{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}
];
var result = mergeArraysDeep(array1, array2)
console.log(result);
ES6 version that uses Map, Map.prototype.values(), and array spread:
const mergeArraysDeep = (arr1, arr2) => {
return [...arr1.concat(arr2).reduce((hash, item) => {
const current = hash.get(item.id);
if(!current) {
hash.set(item.id, item);
} else if (Array.isArray(current.data)) {
current.data = mergeArraysDeep(current.data, item.data);
} else {
current.data = item.data;
}
return hash;
}, new Map()).values()];
}
const array1 = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"test2"},{id:"yz", data:"test3"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"}]}
];
const array2 = [
{id:"456", data:[{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}
];
const result = mergeArraysDeep(array1, array2)
console.log(result);

Finally this is what worked for me. Thanks to #Geeky for showing the way:
function mergeArrays(arr1, arr2) {
var arr3, arrIdx = [];
if (!arr1 || arr1.length ==0) return arr2
for (var i in arr1) {
var shared = false;
for (var j in arr2)
if (arr2[j].id == arr1[i].id) {
shared = true;
joined = _.mergeWith({},arr1[i],arr2[j], function (a,b) {
if (_.isArray(a)) return b.concat(a)})
arr3.push(joined);
break;
}
if (!shared) {
arr3.push(arr1[i]);
}
}
for (var k in arr2) {
if (arrIdx[k] !=k) arr3.push(arr2[k])
}
return arr3
}

Related

Change particular key in nested array to object in javascript

If the fields key in a object is array, change the first value of arrays as a key value pair object in javascript.
var obj =
[
{ id:1, fields:["item", "2", "list"]},
{ id:2, fields:["sample", "1", "check"]}
]
function newObj(obj) {
let objFields = {};
modifiedObj.fields.forEach(field => objFields[field] = field);
modifiedObj.fields= objFields;
return modifiedObj;
}
var result = this.newObject(obj)
Expected Output
{
item: "item",
sample: "sample"
}
Try this:
var obj =
[
{ id:1, fields:["item", "2", "list"]},
{ id:2, fields:["sample", "1", "check"]}
]
function newObject(obj) {
let objFields = {};
obj.forEach(e => {
if(e.fields && e.fields.length>0)
objFields[e.fields[0]] = e.fields[0];
});
return objFields;
}
var result = this.newObject(obj);
console.log(result);
Here is a functional approach that makes use of Object.assign(), spread operator, and Array.map() to create the object you need.
const input = [
{ id: 1, fields: ["item", "2", "list"] },
{ id: 2, fields: ["sample", "1", "check"] }
];
const process = (input) => (Object.assign(...input.map(({ fields }) => (
fields.length ? { [fields[0]]: fields[0] } : {}
))));
console.log(process(input));
Your snippet was close, you just needed to clean up the variable names, and then using map makes it a bit neater too:
const obj = [
{id: 1, fields: ["item", "2", "list"]},
{id: 2, fields: ["sample", "1", "check"]}
]
function newObj(inputArray) {
let outputObject = {};
inputArray.map(item => item.fields[0])
.forEach(field => outputObject[field] = field);
return outputObject;
}
var result = newObj(obj)
console.log(result)

Relate and merge array of same Department

I am working on an application where I need to get combine the object of same department based on the
conditions provided in the second Array and attach the relation to the object.
let inArr1 = [{"D1D2":"AND"},{"D3D4":"OR"}]
let inArr2 =[{"ID":"1","NAME":"KEN","DEPT1":"CSE"},
{"ID":"2","NAME":"MARK","DEPT2":"IT"},
{"ID":"3","NAME":"TOM","DEPT3":"ECE"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB"},
{"ID":"5","NAME":"TIM","DEPT5":"SEC"}
]
Output
outArr ={
[{"ID":"1","NAME":"KEN","DEPT1":"CSE","REL":"AND"},
{"ID":"2","NAME":"MARK","DEPT2":"IT","REL":"AND"}], //Arr1
[{"ID":"3","NAME":"TOM","DEPT3":"ECE","REL":"OR"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB","REL":"OR"}], //Arr2
[{"ID":"5","NAME":"TIM","DEPT5":"SEC"}] //Arr3
}
Code:
let condArr=[],outArr,i=1;
inArr1.forEach(condt => {
let dept = Object.keys(condt)[0];
let tmparr = dept.split("D");
tmparr.shift()
condArr.push(tmparr)
});
inArr2.forEach(condt => {
if(condArr.includes(inArr2.D+i)){
i++;
outArr.push(inArr2);
}
});
Your code has a bit confused logic, i would suggest rather this
let inArr1 = [{"D1D2":"AND"},{"D3D4":"OR"},{"D5D6":"AND"}]
let inArr2 =[{"ID":"1","NAME":"KEN","DEPT1":"CSE"},
{"ID":"2","NAME":"MARK","DEPT2":"IT"},
{"ID":"3","NAME":"TOM","DEPT3":"ECE"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB"},
{"ID":"5","NAME":"TIM","DEPT5":"SEC"},
{"ID":"6","NAME":"TLA","DEPT6":"SEC"},
]
// first lets create object of ids as keys and conditions as values
const [keys, conditions] = inArr1.reduce((agg, cond, index) => {
Object.entries(cond).forEach(([key, value]) => {
key.split('D').forEach(v => { if (v) agg[0][v] = { value, index }})
agg[1].push([])
})
return agg
}, [{}, []]) // {1: "AND", 2: "AND", 3: "OR", 4: "OR"}
conditions.push([])
// and now just map over all elements and add condition if we found id from the keys
inArr2.forEach(item => {
const cond = keys[item.ID]
if (cond) conditions[cond.index].push({...item, REL: cond.value})
else conditions[conditions.length - 1].push(item)
})
const res = conditions.filter(v => v.length)
console.log(res)
You could store the goups by using the ID and use new objects.
let inArr1 = [{ D1D2: "AND" }, { D3D4: "OR" }],
inArr2 = [{ ID: "1", NAME: "KEN", DEPT1: "CSE" }, { ID: "2", NAME: "MARK", DEPT2: "IT" }, { ID: "3", NAME: "TOM", DEPT3: "ECE" }, { ID: "4", NAME: "SHIV", DEPT4: "LIB" }, { ID: "5", NAME: "TIM", DEPT5: "SEC" }],
groups = inArr1.reduce((r, o) => {
Object.entries(o).forEach(([k, REL]) => {
var object = { REL, group: [] };
k.match(/[^D]+/g).forEach(id => r[id] = object);
});
return r;
}, {}),
grouped = inArr2.reduce((r, o) => {
var { REL, group } = groups[o.ID] || {};
if (group) {
if (!group.length) r.push(group);
group.push(Object.assign({}, o, { REL }));
} else {
r.push([o]);
}
return r;
}, []);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
can try other solution:
let inArr1 = [{ D1D2: "AND" }, { D3D4: "OR" }, { D6D7: "XOR" }];
let inArr2 = [
{ ID: "1", NAME: "KEN", DEPT1: "CSE" },
{ ID: "2", NAME: "MARK", DEPT2: "IT" },
{ ID: "3", NAME: "TOM", DEPT3: "ECE" },
{ ID: "4", NAME: "SHIV", DEPT4: "LIB" },
{ ID: "5", NAME: "TIM", DEPT5: "SEC" },
{ ID: "9", NAME: "BAR", DEPT5: "XYZ" },
{ ID: "6", NAME: "FOO", DEPT5: "XYZ" },
];
let unmatchedArr = []
let matchedArr = inArr2.reduce((acc, obj) => {
// getting index matched from inArr1 objects key
const indexMatched = getIndexMatch(obj.ID);
// creating index if not exists
if (!acc[indexMatched] && indexMatched !== null) acc[indexMatched] = [];
// if some index matched it merge current obj with DEL property with inArr1[indexMatched] key => value
return indexMatched !== null
? acc[indexMatched].push({
...obj,
DEL: inArr1[indexMatched][Object.keys(inArr1[indexMatched])[0]]
})
// pushing on unmatchedArr
: unmatchedArr.push(obj)
, acc
}, []);
function getIndexMatch(id) {
for (const [index, obj] of inArr1.entries()) {
for (const key of Object.keys(obj)) {
// spliting only digits of the current key of object
if (key.match(/\d/g).includes(id)) return index; // returning index of inArr1 if is included
}
}
return null;
}
// merging arrays
const result = [...matchedArr, unmatchedArr];
console.log(result);

how to get the corresponding type from fruit names array?

Get the corresponding type in the object, and then traverse the array of push objects, but I can't think of a better way to solve the desired result below.
I want a good return as follows:
[{
"id": 1,
"type": "one",
"name": ["apple","apricot"]
},
{
"id": 3,
"type": "two",
"name": ["avocado"]
}]
var result = [{
"id": 1,
"type": "one",
"name": "apple"
}, {
"id": 2,
"type": "one",
"name": "apricot"
},
{
"id": 3,
"type": "two",
"name": "avocado"
}
]
Array.prototype.unique = function() {
var hash = {},
len = this.length,
result = [];
for (var i = 0; i < len; i++) {
if (!hash[this[i].type]) {
result.push(this[i].type);
hash[this[i].type] = true;
}
}
return result;
}
console.log(result)
console.log(result.unique())
var cArr = result.unique()
var arr = []
cArr.forEach(function(prop) {
result.map(function(item) {
if (prop == item.type) {
console.log(item)
arr.push({
...item,
[`user_${item.id}`]: item.user,
})
}
})
})
console.log(arr)
You can do this with reduce quite easily:
var input = [
{ id: 1, type: "one", name: "apple"},
{ id: 2, type: "one", name: "apricot" },
{ id: 3, type: "two", name: "avocado" }
];
// Make sure `unique` doesn't already exist on the Array prototype
if (!('unique' in Array.prototype)) {
Array.prototype.unique = function () {
// iterate over the array
const temp = this.reduce((acc, current) => {
// Desstructure the id, type, and name from the current object
const { id, type, name } = current;
// If an key with the value of `type` doesn't exist
// on the accumulator, add a new object with name set
// to an empty array
acc[type] = acc[type] || { id, type, name: [] };
// Push the name in the current object to the name array
acc[type].name.push(name);
// Return the accumulator for the next iteration
return acc;
// Note: the initial accumulator value is an object
}, {});
// Then simply return the values from the accumulated object
return Object.values(temp);
}
}
console.log(input.unique())

array.map() not returning a single array

I have the code below which I expect to map the result from the nested array and return a single array having both id's but I get 2 arrays instead. Can someone please guide me on what I'm doing wrongly?
arrayVal = [{
sources: {
data: [{
id: 1
}]
}
},
{
sources: {
data: [{
id: 2
}]
}
}
]
for (let sub of arrayVal) {
let result = sub.sources.data.map(x => (x.id))
console.log(result)
}
Right now, you're calling map for each element in arrayVal, so you get two arrays. Use reduce instead, to transform an array of objects into another array that's not necessarily one-to-one with the input elements:
const arrayVal=[{sources:{data:[{id:1}]}},{sources:{data:[{id:2}]}}];
const result = arrayVal.reduce((a, { sources: { data } }) => (
[...a, ...data.map(({ id }) => id)]
), []);
console.log(result)
Try following
var arrayVal = [{sources: {data: [{id: 1}]}},{sources: {data: [{id: 2}]}}];
// Create an array on sources.data and merge it into 1 collection (array)
var result = arrayVal.reduce((a, c) => [...a, ...c.sources.data.map(({id}) => id)], []);
console.log(result);
For reference, Array.reduce
Also, you can improve your code as follows
var arrayVal = [{sources: {data: [{id: 1}]}},{sources: {data: [{id: 2}]}}];
let result = [];
for (let sub of arrayVal) {
result.push(sub.sources.data.map(x => (x.id)));
}
console.log([].concat(...result))
arrayVal = [{
sources: {
data: [{
id: 1
}]
}
},
{
sources: {
data: [{
id: 2
}]
}
}
]
let result = [];
for (let sub of arrayVal) {
result = result.concat(sub.sources.data.map(x => (x.id)))
}
console.log(result)
I think concat is what you were missing here, Hope this is what you trying to achieve
Try this
arrayVal = [{
sources: {
data: [{
id: 1
}]
}
},
{
sources: {
data: [{
id: 2
}]
}
}
]
let result = arrayVal.map((x) => x.sources.data[0].id)
console.log(result)
You can do something like this:
arrayVal = [{
sources: {
data: [{
id: 1
}]
}
},
{
sources: {
data: [{
id: 2
}]
}
}
]
var flat = arrayVal.reduce(function(prev,curr,cI){
prev.push(curr.sources.data.map(x => (x.id))[0]);
return prev; // ********* Important ******
}, []);

Javascript Fill array with missing object and value

I have an array like bellow each index contains different set of objects,I want to create an uniformal data where object missing in each index will with Value:0 ,
var d = [
[
{axis:"Email",value:59,id:1},
{axis:"Social Networks",value:56,id:2},
],
[
{axis:"Sending Money",value:18,id:6},
{axis:"Other",value:15,id:7},
]
];
how can I get an array like bellow using above above array
var d = [
[
{axis:"Email",value:59,id:1},
{axis:"Social Networks",value:56,id:2},
{axis:"Sending Money",value:0,id:6},
{axis:"Other",value:0,id:7},
],
[
{axis:"Email",value:0,id:1},
{axis:"Social Networks",value:0,id:2},
{axis:"Sending Money",value:18,id:6},
{axis:"Other",value:15,id:7},
]
];
There are two functions:
getAllEntries that find all objects and stores them into a variable accEntries. Then accEntries is used to search for all occurrences in a sub-array of d. This whole process is done in checkArray.
checkArray is used to fetch all found and not-found entries in d. Both Arrays (found and not-found) are then used to build a new sub-array that contains either found entries with certain values and/or not-found entries with values of 0.
Hope this helps:
var d = [
[
{
axis: 'Email',
value: 59,
id: 1
},
{
axis: 'Social Networks',
value: 56,
id: 2
},
],
[
{
axis: 'Sending Money',
value: 18,
id: 6
},
{
axis: 'Other',
value: 15,
id: 7
},
]
];
function getAllEntries(array) {
var uniqueEntries = [];
array.forEach(function (subarray) {
subarray.forEach(function (obj) {
if (uniqueEntries.indexOf(obj) === - 1) uniqueEntries.push(obj);
});
});
return uniqueEntries;
}
function checkArray(array, acceptedEntries) {
var result = [];
array.forEach(function (subArray) {
var subResult = [];
var foundEntries = [];
subArray.forEach(function (obj) {
if (foundEntries.indexOf(obj.axis) === - 1) foundEntries.push(obj.axis);
});
var notFound = acceptedEntries.filter(function (accepted) {
return foundEntries.indexOf(accepted.axis) === - 1;
});
foundEntries.forEach(function (found) {
subArray.forEach(function (obj) {
if (obj.axis === found) subResult.push(obj);
});
});
notFound.forEach(function (notfound, index) {
subResult.push({
axis: notfound.axis,
value: 0,
id: notfound.id
});
});
result.push(subResult);
});
return result;
}
var accEntries = getAllEntries(d);
var result = checkArray(d, accEntries);
console.log(result);
You can loop over the array to find all the unique objects and then again loop over to push the values that are not present comparing with the array of objects of unique keys.
You can use ES6 syntax to find if an object with an attribute is present like uniKeys.findIndex(obj => obj.axis === val.axis); and the to push with a zero value use the spread syntax like d[index].push({...val, value: 0});
Below is the snippet for the implementation
var d = [
[
{axis:"Email",value:59,id:1},
{axis:"Social Networks",value:56,id:2},
],
[
{axis:"Sending Money",value:18,id:6},
{axis:"Other",value:15,id:7},
{axis:"Social Networks",value:89,id:2},
]
];
var uniKeys = [];
$.each(d, function(index, item) {
$.each(item, function(idx, val){
const pos = uniKeys.findIndex(obj => obj.axis === val.axis);
if(pos == - 1) {
uniKeys.push(val);
}
})
})
$.each(d, function(index, item) {
var temp = [];
$.each(uniKeys, function(idx, val){
const pos = item.findIndex(obj => obj.axis === val.axis);
if(pos == - 1) {
d[index].push({...val, value: 0});
}
})
})
console.log(d);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
How about a short shallowCopy function (Object.assign is not available in IE) and otherwise less than 10 new lines of code?
var d = [
[
{axis:"Email",value:59,id:1},
{axis:"Social Networks",value:56,id:2}
],
[
{axis:"Sending Money",value:18,id:6},
{axis:"Other",value:15,id:7}
]
];
var newD_0 = [shallowCopy(d[0][0]), shallowCopy(d[0][1]), shallowCopy(d[1][0]), shallowCopy(d[1][1])];
var newD_1 = [shallowCopy(d[0][0]), shallowCopy(d[0][1]), shallowCopy(d[1][0]), shallowCopy(d[1][1])];
newD_0[2].id = 0;
newD_0[3].id = 0;
newD_1[0].id = 0;
newD_1[1].id = 0;
d = [newD_0, newD_1];
function shallowCopy(obj) {
var copy = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = obj[key];
}
}
return copy;
}
console.log(JSON.stringify(d));
RESULT:
[
[
{
"axis":"Email",
"value":59,
"id":1
},
{
"axis":"Social Networks",
"value":56,
"id":2
},
{
"axis":"Sending Money",
"value":18,
"id":0
},
{
"axis":"Other",
"value":15,
"id":0
}
],
[
{
"axis":"Email",
"value":59,
"id":0
},
{
"axis":"Social Networks",
"value":56,
"id":0
},
{
"axis":"Sending Money",
"value":18,
"id":6
},
{
"axis":"Other",
"value":15,
"id":7
}
]
]

Categories

Resources