Pick one field from Array of Objects recursively - javascript

I am looking for a way using JavaScript / Lodash to retrieve the same hirearchy as the input array of objects but want to retain only selected fields.
I could also formulate this question as doing a deep copy of an array of objects retaining only certain fields.
For example, given the following array:
[
{
"id": "q1",
"text": "Q1 text",
"children": [
{
"id": "q11",
"text": "t",
"children": [
{
"id": "q111",
"text": "t"
},
{
"id": "q112",
"text": "t"
}
]
}
]
},
{
"id": "q2",
"text": "e",
"children": [
{
"id": "q22",
"text": "e"
}
]
},
{
"id": "q3",
"text": "e"
}
]
The output should be as below. This is exactly the same as array of objects above but keeps only id and children's ids. The children can be any level deep.
[
{
"id": "q1",
"children": [
{
"id": "q11",
"children": [
{
"id": "q111",
},
{
"id": "q112"
}
]
}
]
},
{
"id": "q2",
"children": [
{
"id": "q22",
}
]
},
{
"id": "q3"
}
]

You can make a function that takes an array and maps it to objects with just the id and children. To set the id, just copy the id, to set the children on the returned object pass the children array back into the function recursively:
let arr = [{"id": "q1","text": "Q1 text","children": [{"id": "q11","text": "t","children": [{"id": "q111","text": "t"},{"id": "q112","text": "t"}]}]},{"id": "q2","text": "e","children": [{"id": "q22","text": "e"}]},{"id": "q3","text": "e"}]
const justIDs = (arr) => arr.map(({id, children}) => {
let ret = {id}
if(children) ret.children = justIDs(children)
return ret
})
let filtered = justIDs(arr)
console.log(filtered)

and lodash, love lodash, learn lodash...
function omitKeysDeep(input, keys) {
if(!_.isArray(keys)) throw new Error('omitKeys expected an array');
return _.map(input, (elem) => {
if(elem.children) elem.children = omitKeysDeep(elem.children, keys);
return _.omit(elem, keys);
});
}
omitKeysDeep(a, ['text']);
OR... instead of _.omit(..) to remove unwanted keys you could use _.pick(...) to specify only wanted keys:
function pickKeysDeep(input, keys) {
if(!_.isArray(keys)) throw new Error('pickKeys expected an array');
return _.map(input, (elem) => {
if(elem.children) elem.children = pickKeysDeep(elem.children, keys);
return _.pick(elem, keys);
});
}
pickKeysDeep(a, ['id', 'children']);

Here's a non-recursive approach that uses an explicit stack and a set for fast lookup in cases when you have many keys to prune out. This is a general solution that should work on any keys you throw at it and doesn't mutate the original array.
const data = [
{
"id": "q1",
"text": "Q1 text",
"children": [
{
"id": "q11",
"text": "t",
"children": [
{
"id": "q111",
"text": "t"
},
{
"id": "q112",
"text": "t"
}
]
}
]
},
{
"id": "q2",
"text": "e",
"children": [
{
"id": "q22",
"text": "e"
}
]
},
{
"id": "q3",
"text": "e"
}
];
const removeKeys = (arr, keys) => {
const keep = new Set(keys);
const res = [];
const stack = [[arr, res]];
while (stack.length) {
const [curr, cpy] = stack.pop();
if (Array.isArray(curr)) {
curr.forEach((e, i) => {
cpy[i] = {};
for (const k in e) {
if (keep.has(k)) {
cpy[i][k] = e[k];
stack.push([e[k], cpy[i][k]]);
}
}
});
}
}
return res;
};
console.log(JSON.stringify(removeKeys(data, ["id", "children"]), null, 4));

Here's my version which does work recursively.
/**
* Like _.pick() but will also map over arrays implicitly.
* ie. path 'a.b.c' will transform {a:[{b:{c:1,d:2}}]} => {a:[{b:{c:1}}]}
*
* #param {object} o - Object to copy.
* #param {string[]} paths - List of paths to include.
* #returns {mixed} - Copied object.
*/
Utils.pickDeep = (o, paths) => {
if (Array.isArray(o)) {
return _.map(o, v=>
Utils.pickDeep(v, paths));
}
else if (null != o && 'object' === typeof o) {
const result = {};
for (const path of paths) {
const parts = path.split('.');
const part = parts.shift();
result[part] = o[part];
if (parts.length < 1) {
// do not recurse
}
else {
// recurse
result[part] = Utils.pickDeep(_.get(o, [part]), [parts.join('.')]);
}
}
return result;
}
else {
return o;
}
};
and
/**
* Like _.omit() but will also map over arrays implicitly.
* ie. path 'a.b.c' will transform {a:[{b:{c:1,d:2}}],e:4} => {a:[{b:{d:2}}],e:4}
*
* #param {object} o - Object to copy.
* #param {string[]} paths - List of paths to exclude.
* #returns {mixed} - Copied object.
*/
Utils.omitDeep = (o, paths) => {
if (Array.isArray(o)) {
return _.map(o, v=>
Utils.omitDeep(v, paths));
}
else if (null != o && 'object' === typeof o) {
const result = { ...o };
for (const path of paths) {
const parts = path.split('.');
const part = parts.shift();
delete result[part];
if (parts.length < 1) {
// do not recurse
}
else {
// recurse
result[part] = Utils.omitDeep(_.get(o, [part]), [parts.join('.')]);
}
}
return result;
}
else {
return o;
}
};

Related

Get values for a matching key recursively from object

I have this json object
{
"data": {
"user": {
"user_info": {
"id": "AoGC2HQ9vedHmzcMX"
},
"product": [
{
"node": {
"id": "NzcxNzU2ODU1ODM1",
"feedback": {
"raters": {
"nodes": [
{
"id": "1",
"name": "Dan"
},
{
"id": "2",
"name": "Allen"
},
{
"id": "3",
"name": "Williams"
}
]
},
"commentors": {
"nodes": [
{
"id": "001",
"name": "Kent"
},
{
"id": "002",
"name": "Jay"
}
]
}
}
}
}
]
}
}
}
So how do I make it to get values of id If the parent property matches the desired key name, In this example I want to get all id's from raters.nodes only.
so expected result is
[1,2,3]
I know can do obj.data.user.product[0].node.feedback.raters.nodes and loop through that, but that is not how I want and the object tree occasionally changes.
I have used this recursive function
const recursiveSearch = (obj, searchKey, results = []) => {
const r = results;
Object.keys(obj).forEach(key => {
const value = obj[key];
if(key === searchKey && typeof value !== 'object'){
r.push(value);
}else if(typeof value === 'object'){
recursiveSearch(value, searchKey, r);
}
});
return r;
};
//returns all id's
While it works, it returns all id values, so how do I improve it? If not, how do I make this possible?
I think you want to really do this in 2 steps,..
First make a function to get the root node your looking for, and then you can just use map like normal.
Below is an example.
var data = JSON.parse("{\"data\":{\"user\":{\"user_info\":{\"id\":\"AoGC2HQ9vedHmzcMX\"},\"product\":[{\"node\":{\"id\":\"NzcxNzU2ODU1ODM1\",\"feedback\":{\"raters\":{\"nodes\":[{\"id\":\"1\",\"name\":\"Dan\"},{\"id\":\"2\",\"name\":\"Allen\"},{\"id\":\"3\",\"name\":\"Williams\"}]},\"commentors\":{\"nodes\":[{\"id\":\"001\",\"name\":\"Kent\"},{\"id\":\"002\",\"name\":\"Jay\"}]}}}}]}}}");
function getRoot(data, search) {
function get(path, data) {
for (const [k, v] of Object.entries(data)) {
if (v instanceof Object) {
const pp = `${path}.${k}`;
if (pp.slice(-search.length) === search) {
return v;
}
const r = get(`${path}.${k}`, v);
if (r) return r;
}
}
}
return get('', data);
}
const r = getRoot(data, 'raters.nodes');
console.log(r && r.map(i => i.id));

Reorder json in js

I am building a tree, and the order I want is name, desc, then children.
So the json I got is in different order. How do you reorder it or is there a better way to write this code, taking consideration of multiple branches, and may be extra element can be add to the input?
So here is my code:
var arry = [{
"name": "J",
"target": "L",
"desc": "2"
},
{
"name": "L",
"target": "A",
"desc": "1"
},
{
"name": "S",
"target": "L",
"desc": "3"
}
];
function toJSON(data) {
var root = data.find(function(x) {
return !data.some(function(y) {
return y.name === x.target
});
}).target;
console.log(root)
var desc = data.find(function(x) {
return !data.some(function(y) {
return y.name === x.target
});
}).desc;
var b = data.reduce(function(acc, x) {
acc[x.target] = acc[x.target] || [];
acc[x.target].push(x.name);
return acc;
}, {});
var tree = buildTree(root, b);
function buildTree(name, branches, desc) {
var tree = {
'name': name
};
if (branches[name]) {
tree.children = branches[name].map(function(x) {
return buildTree(x, branches, desc)
});
for (var child in branches[name]) {
var x = arry.find(function(i) {
return (i.name === branches[name][child] && i.target === name)
})
tree.children[child].desc = x.desc
}
}
return tree;
}
if (tree.name === root) {
tree.desc = root
}
for (var i in tree) {
console.log(i)
}
return tree;
} // JavaScript name code
var a = toJSON(arry)
console.log(JSON.stringify(a, null, 2))
The result I got is:
{
"name": "A",
"children": [
{
"name": "L",
"children": [
{
"name": "J",
"desc": "2"
},
{
"name": "S",
"desc": "3"
}
],
"desc": "1"
}
],
"desc": "A"
}
The result I want is:
{
"name": "A",
"desc": "A",
"children": [
{
"name": "L",
"desc": "1",
"children": [
{
"name": "J",
"desc": "2"
},
{
"name": "S",
"desc": "3"
}
]
}
]
}
I agree with the question "Why does order matter"
But if you really want...
When creating object literals the keys/properties remain in the order in which they are/were defined (unless the keys are integers, then they will be in numerical order). On the line where you create your tree node you define an object but only put the name property on it. You then add children, then finally set the desc property. If you instead define your object right away they will have that order and you can later mutate the values however you want.
const tree = {
name,
desc: undefined,
children: undefined,
};
var arry = [
{
name: "J",
target: "L",
desc: "2"
},
{
name: "L",
target: "A",
desc: "1"
},
{
name: "S",
target: "L",
desc: "3"
}
];
function toJSON(data) {
var root = data.find(function(x) {
return !data.some(function(y) {
return y.name === x.target;
});
}).target;
console.log(root);
var desc = data.find(function(x) {
return !data.some(function(y) {
return y.name === x.target;
});
}).desc;
var b = data.reduce(function(acc, x) {
acc[x.target] = acc[x.target] || [];
acc[x.target].push(x.name);
return acc;
}, {});
var tree = buildTree(root, b);
function buildTree(name, branches, desc) {
// define your tree node shape here
var tree = {
name,
desc: undefined,
children: undefined
};
if (branches[name]) {
tree.children = branches[name].map(function(x) {
return buildTree(x, branches, desc);
});
for (var child in branches[name]) {
var x = arry.find(function(i) {
return i.name === branches[name][child] && i.target === name;
});
tree.children[child].desc = x.desc;
}
}
return tree;
}
if (tree.name === root) {
tree.desc = root;
}
for (var i in tree) {
console.log(i);
}
return tree;
} // JavaScript name code
var a = toJSON(arry);
console.log(JSON.stringify(a, null, 2));

How i can get data from another object?

Plunker
I have two structures - ingredients and recipes
[{
"id":"1",
"name": "Cucumber"
},
..
]
and
[{
"id":"1",
"name": "Salad1",
"recipein":[1, 3, 5]
}, {
...
}
]
and i want to show names of ingredients in each salad by press a button.
I filtered object to get ID of object, then i try to get a array of ingredients
getSalad(param:number) {
this.saladId = this.recipe.filter(rec => {
return rec.id.includes(param);
})
this.getNameOfIngredients(this.saladId)
}
getNameOfIngredients(saladArray:any) {
var ingredientsId = saladArray.map(function(num) {
return num.recipein;
});
i getting array [1,2,4] now i want to show all names of ingredients from this.ingredients with this array of id's.
How can i do this?
Plunker
I made updates in your plunker. I think thats what are you looking for: Plunker
getSalad(param:number) {
this.saladId = this.recipe.filter(rec => +rec.id === param )[0];
if(!this.saladId){
this.currentSalad = "Salad not found";
return;
}
this.currentSalad = this.getNameOfIngredients(this.saladId)
}
getNameOfIngredients(saladArray:any) {
return this.ingredients.filter( ing => {
return saladArray.recipein.indexOf(+ing.id) !== -1;
});
let _ingredients = []
this.ingredients.foreach((ingr)=>{
if(this.ingreIDArry.indexof(ingr.id) > -1){
_ingredients.push(ingr.name)
}
})
return _ingredients
is this what you want?
if you can flatten the array, it would be very straightforward for us to do lookups.
Here is what you could do.
const salads = [{
"id": "1",
"name": "Salad1",
"recipein": [1, 3, 5]
}];
const ingredients = [{
"id": "1",
"name": "Cucumber"
},
{
"id": "2",
"name": "Cucumber2"
},
{
"id": "3",
"name": "Cucumber3"
},
{
"id": "4",
"name": "Cucumber4"
},
{
"id": "5",
"name": "Cucumber5"
}
];
const flattenIngredients = (() => {
const output = {};
ingredients.forEach((ingredient) => {
output[ingredient.id] = ingredient;
});
return output;
})();
const getSalad = (saladId) => {
const filteredSalad = salads.filter((salad) => {
return saladId == salad.id;
});
if (filteredSalad.length > 0) {
const salad = filteredSalad[0];
return salad.recipein.map((receip) => flattenIngredients[receip].name);
}
}
console.log(getSalad(1));

How to merge objects with the same properties into an Array?

I would like to merge 2 objects with the same properties into an Array.
Take this for an example:
object1 = {"id":1,
"name":name1,
"children":[{"id":2,"name":name2}]
};
object2 = {"id":3,
"name":name3,
"children":[{"id":4,"name":name4}]
};
object3 = {"id":1,
"name":name1,
"children":[{"id":6,"name":name6}]
};
var result = Object.assign(result,object1,object2,object3);
Expected result:
JSON.stringify([result]) =[
{"id":1,
"name":name1,
"children":[{"id":2,"name":name2},
{"id":6,"name":name6}]
},
{"id":3,
"name":name3,
"children":[{"id":4,"name":name4}]
}
]
Actual result:
JSON.stringify([result]) = [
{"id":3,
"name":name3,
"children":[{"id":4,"name":name4}]
}
]
Seems like Object.assign() isn't the way to go... as it will overwrite, I do not want it to overwrite, I want them to merge instead. Is there a right way to do this?
As so often, Array.prototype.reduce provides a good base for an approach like e.g. this one ...
var obj1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2" }]
};
var obj2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4" }]
};
var obj3 = {
"id": 1,
"name": "name1",
"children": [{ "id": 6, "name": "name6" }]
};
// Expected result: [{
// "id": 1,
// "name": name1,
// "children": [
// { "id": 2, "name": "name2" },
// { "id": 6, "name": "name6" }
// ]
// }, {
// "id": 3,
// "name": "name3",
// "children": [{"id": 4, "name": "name4" }]
// }]
function mergeEquallyLabeledTypes(collector, type) {
var key = (type.name + '#' + type.id); // identity key.
var store = collector.store;
var storedType = store[key];
if (storedType) { // merge `children` of identically named types.
storedType.children = storedType.children.concat(type.children);
} else {
store[key] = type;
collector.list.push(type);
}
return collector;
}
var result = [obj1, obj2, obj3].reduce(mergeEquallyLabeledTypes, {
store: {},
list: []
}).list;
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }
Edit Note
After having been informed about changed requirements, that need to deal with a nested pattern, I will change my first provided approach into a generic solution. It will be not that difficult since there is a generically repeated pattern within the data structure. Thus I just need to make the already existing reducer function self recursive. A recursion step will be triggered after having finished a complete reducing cycle on any provided list ...
var obj1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2", "children": [{ "id": 8, "name": "name8" }] }]
};
var obj2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4", "children": [{ "id": 9, "name": "name9" }] }]
};
var obj3 = {
"id": 1,
"name": "name1",
"children": [{ "id": 6, "name": "name6", "children": [{ "id": 10, "name": "name10" }] }]
};
var obj4 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4", "children": [{ "id": 11, "name": "name11" }] }]
};
function mergeEquallyLabeledTypesRecursively(collector, type, idx, list) {
var key = (type.name + '#' + type.id); // identity key.
var store = collector.store;
var storedType = store[key];
if (storedType) { // merge `children` of identically named types.
storedType.children = storedType.children.concat(type.children);
} else {
store[key] = type;
collector.list.push(type);
}
// take repetitive data patterns into account ...
if (idx >= (list.length - 1)) {
collector.list.forEach(function (type) {
// ... behave recursive, when appropriate.
if (type.children) {
type.children = type.children.reduce(mergeEquallyLabeledTypesRecursively, {
store: {},
list: []
}).list;
}
});
}
return collector;
}
var result = [obj1, obj2, obj3, obj4].reduce(mergeEquallyLabeledTypesRecursively, {
store: {},
list: []
}).list;
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }
This might be what your after, please note it's not recursive now recursive. But your example data doesn't appear to be anyway.
const object1 = {"id":1,
"name":"name1",
"children":[{"id":2,"name":"name2"}]
};
const object2 = {"id":3,
"name":"name3",
"children":[{"id":4,"name":"name4"}]
};
const object3 = {"id":1,
"name":"name1",
"children":[
{"id":6,"name":"name6"},
{"id":7,"name":"name7"},
{"id":6,"name":"name6"}
]
};
function merge(arr) {
const idLinks = {};
const ret = [];
arr.forEach((r) => {
if (!idLinks[r.id]) idLinks[r.id] = [];
idLinks[r.id].push(r);
});
Object.keys(idLinks).forEach((k) => {
const nn = idLinks[k];
const n = nn[0];
for (let l = 1; l < nn.length; l ++) {
if (nn[l].children) {
if (!n.children) n.children = [];
n.children = n.children.concat(nn[l].children);
}
}
if (n.children && n.children.length) n.children = merge(n.children);
ret.push(n);
});
return ret;
}
var result = merge([object1,object2,object3]);
console.log(result);
/* There are two cases :
a) No duplicate children
b) Duplicate children either in (same object || different object|| both)
*/
/* =============== */
/* Case a) */
const util = require('util');
var object1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2" }]
};
var object2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4" }]
};
var object3 = {
"id": 1,
"name":"name1",
"children":[{"id":6,"name":"name6"}]
};
var arr = [object1,object2,object3];
var uniqueIds = [];
var filteredArray = [];
var uniqueId='';
arr.map((item,i,array)=>{
uniqueId =uniqueIds.indexOf(item.id);
uniqueId = uniqueId+1;
uniqueIds = [...uniqueIds,item.id];
if(!uniqueId){
filteredArray[i] = item;
}
if(uniqueId){
filteredArray[uniqueId-1]['children'] = [...(array[uniqueId-1].children),...(item.children)];
}
});
console.log(util.inspect(filteredArray,false,null));
/* ============================================
Case b)
Dealing with the worst case of having duplicate children in both same
and different objects
*/
object1 = {"id":1,
"name":'name1',
"children":[{"id":2,"name":'name2'},
{"id":2,"name":'name2'}]
};
object2 = {"id":3,
"name":'name3',
"children":[{"id":4,"name":'name4'}]
};
object3 = {"id":1,
"name":'name1',
"children":[{"id":6,"name":'name6'},
{"id":7,"name":'name7'},
{"id":2,"name":'name2'}]
};
arr = [object1,object2,object3];
uniqueIds = [];
uniqueId = '';
arr.map((item,i,array)=>{
uniqueId =uniqueIds.indexOf(item.id);
uniqueId = uniqueId+1;
uniqueIds = [...uniqueIds,item.id];
if(!uniqueId){
filteredArray[i] = item;
}
if(uniqueId){
filteredArray[uniqueId-1]['children'] = [...(array[uniqueId-1].children),...(item.children)];
}
/*Removing duplicate children entries*/
filteredArray[uniqueIds.indexOf(item.id)]['children'] = filteredArray[uniqueIds.indexOf(item.id)]['children']
.filter((elem, index, self) => self.findIndex((t) => {return t.id === elem.id}) === index)
})
console.log(util.inspect(filteredArray,false,null));
In functional programming way with es6 standards. I am assuming children array also contains duplicates. I enclosed the code in closures.
See the following link why I used util to print all the object in node console.log()
How can I get the full object in Node.js's console.log(), rather than '[Object]'?
(function() {
'use strict';
const util = require('util');
/** string constants */
const ID = 'id';
const CHILDREN = 'children';
/* Objects to modify */
const object1 = {
"id": 1,
"name": "name1",
"children": [
{ "id": 2, "name": "name2" },
{ "id": 5, "name": "name5" },
{ "id": 7, "name": "name7" }
]
};
const object2 = {
"id": 3,
"name": "name3",
"children": [
{ "id": 4, "name": "name4" }
]
};
const object3 = {
"id": 1,
"name": "name1",
"children": [
{ "id": 5, "name": "name5" },
{ "id": 6, "name": "name6" }
]
};
/**
* Concates the arrays
* #param { array } - a
* #param { array } - b
*/
const merge = (a, b) => {
return a.concat(b);
};
/**
* Removes Duplicates from the given array based on ID
* #param { array } - array to remove duplicates
* #return { array } - array without duplicates
*/
const removeDuplicates = (arr) => {
return arr.filter((obj, pos, arr) => {
return arr.map((m) => {
return m[ID];
}).indexOf(obj[ID]) === pos;
});
}
/**
* Groups items in array with particular key
* Currying technique
* #param { prop } - key to group
* #return { () => {} } - Method which in turn takes array as argument
*/
const groupBy = (prop) => (array) => {
return array.reduce((groups, item) => {
const val = item[prop];
groups[val] = groups[val] || [];
groups[val].push(item);
return groups;
}, {});
}
/**
* Object containing grouped-items by particuar key
*/
const grouped = groupBy(ID)([object1, object2, object3]);
/**
* Removing the duplicates of children
* Remember map also mutates the array of objects key's value
* but not data type
*/
Object.keys(grouped).map((key, position) => {
grouped[key].reduce((a, b) => {
a[CHILDREN] = removeDuplicates(a[CHILDREN].concat(b[CHILDREN]));
});
});
/**
* Desired final output
*/
const final = Object.keys(grouped)
.map((key) => removeDuplicates(grouped[key]))
.reduce(merge, []);
console.log(util.inspect(final, false, null))})();
const object1 = {
"id":1,
"name":"name1",
"children":[{"id":2,"name":"name2"}]
};
const object2 = {
"id":3,
"name":"name3",
"children":[{"id":4,"name":"name4"}]
};
const object3 = {
"id":1,
"name":"name1",
"children":[{"id":6,"name":"name6"}]
};
var array = [object1,object2,object3];
var array2 = [object1,object2,object3];
function uniquearray(obj){
var result =[];
for(var i=0;i<array.length;i++){
if(obj.id == array[i].id){
result.push(array[i])
array.splice(i,1)
}
}
return result;
}
var arrayofuniarrays = []
for(var i=0;i<array2.length;i++){
arrayofuniarrays.push(uniquearray(array2[i]))
}
for(var i=0;i<arrayofuniarrays.length;i++){
for(var j=1;j<arrayofuniarrays[i].length; j++){
arrayofuniarrays[i][0].children.push(arrayofuniarrays[i][j].children)
arrayofuniarrays[i].splice(j,1)
}
}
var resul = arrayofuniarrays.reduce(function(a, b){return a.concat(b)},[])
console.log(resul)
Here is a sketch example of how to do this. It leverages a mapped type using your id as a key to ensure each item only appears once. It adds all of the children to an array based on the id.
If you needed to enforce the same behaviour on the children, you could use the same technique.
I have split this into multiple iterations to show you the individual parts in play.
Usually, it is more efficient to avoid creating objects that need to be zipped back up if you can.
const object1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2" }]
};
const object2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4" }]
};
const object3 = {
"id": 1,
"name":"name1",
"children":[{"id":6,"name":"name6"}]
};
const all = [object1, object2, object3];
// Use a map like a dictionary to enforce unique keys
const mapped = {};
for (let obj of all) {
if (!mapped[obj.id]) {
mapped[obj.id] = obj;
continue;
}
mapped[obj.id].children.push(obj.children);
}
console.log('Mapped ==> '+JSON.stringify(mapped));
// If you want to convert the mapped type to an array
const result = [];
for (let key in mapped) {
result.push(mapped[key]);
}
console.log('Array ==> '+JSON.stringify(result));
Building on #Peter Seliger's answer here, I derived with the following method to merge arrays with deeply nested children.
Given the following objects:
var obj1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2", children:[{ "id":8, "name": "name8" }] }]
};
var obj2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4", children:[{ "id":9, "name": "name9" }] }]
};
var obj3 = {
"id": 1,
"name": "name1",
"children": [{ "id": 6, "name": "name6", children:[{ "id":10, "name": "name10" }] }]
};
var obj4 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4", children:[{ "id":11, "name": "name11" }] }]
};
First we merge the parents
function mergeEquallyLabeledTypes(collector, type) {
var key = (type.name + '#' + type.id); // identity key.
var store = collector.store;
var storedType = store[key];
if (storedType) { // merge `children` of identically named types.
if(storedType.children)
storedType.children = storedType.children.concat(type.children);
} else {
store[key] = type;
collector.list.push(type);
}
return collector;
}
var result = [obj1, obj2, obj3, obj4].reduce(mergeEquallyLabeledTypes, {
store: {},
list: []
}).list;
Then we merge the children and subchildren if any.
for(let i=0; i<result.length; i++){
var children = result[i].children;
if(children){
var reducedChildren = children.reduce(mergeEquallyLabeledTypes, {store: {}, list: []}).list;
for(let j=0; j<reducedChildren.length; j++){
var subchildren = reducedChildren[j].children;
if(subchildren){
var reducedSubchildren = subchildren.reduce(mergeEquallyLabeledTypes, {store: {}, list: []}).list;
reducedChildren[j].children = reducedSubchildren;
}
}
result[i].children = reducedChildren;
}
}
Finally the result will be what I'll parse into my website.
console.log('result : ', result);
I am able to get the expected result.
// result: [{
// "id": 1,
// "name": name1,
// "children": [
// { "id": 2, "name": "name2", children:[{ "id":8, "name": "name8" }] },
// { "id": 6, "name": "name6", children:[{ "id":10, "name": "name10" }] }
// ]
// }, {
// "id": 3,
// "name": "name3",
// "children": [{"id": 4, "name": "name4", children:[
// { "id":9, "name": "name9" },
// { "id":11, "name": "name11" }
// ]
// }
// ]
// }]
However, this might not be too efficient as I'll need to keep adding on to the merging of children/subchildren method if my tree get nested with more levels. (e.g. subsubchildren, subsubsubchildren and so on...)
Is there any more efficient way to do this?
const object1 = {
id:1,
name:'a',
}
const object2 = {
id:3,
name:'b',
}
const object3 = {
id:1,
name:'c',
}
const originArr = [object1, object2, object3]
const idArr = [object1.id, object2.id, object3.id]
const newIdArr = []
for (let id of idArr) {
if (newIdArr.indexOf(id)) newIdArr.push(id)
}
const result = newIdArr.map(id => {
let names = []
for (obj of originArr) {
if (id === obj.id) names.push(obj.name)
}
return { id, names }
})
console.log(result)

Compare two arrays and update with the new values by keeping the existing objects using javascript

Below are my two arrays .I want to compare them and the resultant array should contain the updated values.Id's are common..
The arrays spans to n levels ie., there is no fixed levels..
The first array ie., the array before updation..
var parentArray1=[
{
"id": 1,
"name": "test",
"context": [
{
"id": 1.1,
"name": "test 1.1"
}
]
},
{
"id": 2,
"name": "test"
},
{
"id": 3,
"name": "test",
"context": [
{
"id": 3.1,
"name": "test 3.1"
}
]
},
{
"id": 4,
"name": "test"
}
]
The operations that i performed are
1.Adding a new Item
2.Updating an existing item
As a result of these two operations the changed values I will be getting in a different array..
ie.,
var changedArray=
[
{
"id": 1,
"name": "test1",
"context": [
{
"id": 1.1,
"name": "Changed test 1.1"
}
]
},
{
"id": 5,
"name": "test5"
}
]
Now I have written a generic function that loops through the parentArray1 and using the unique propertiesI need to either add a new item,if the item is there in the changedArray or update an existing item at any level
The resultant array should be ..
[
{
"id": 1,
"name": "test",
"context": [
{
"id": 1.1,
"name": "Changed test 1.1"
}
]
},
{
"id": 2,
"name": "test"
},
{
"id": 3,
"name": "test",
"context": [
{
"id": 3.1,
"name": "test 3.1"
}
]
},
{
"id": 4,
"name": "test"
},
{
"id": 5,
"name": "test5"
}
]
Generic function:
compareArray(parentArray1, changedArray, ["id"]);
function compareArray(array1, array2, propertyArray) {
var newItem = new Array();
array2.map(function(a1Item) {
array1.map(function(a2Item) {
/ If array loop again /
if (a2Item.constructor === Array) {
compareArray(a2Item, a1Item)
} else {
/ loop the property name to validate /
propertyArray.map(function(property) {
if (a2Item[property]) {
if (a2Item[property] === a1Item[property]) {
a2Item = a1Item
} else {
var isAvailable = _.find(newItem, function(item) {
return item[property] === a1Item[property]
})
if (!isAvailable) {
newItem.push(a1Item);
}
}
}
})
}
});
});
/ Insert the new item into the source array /
newItem.map(function(item) {
array1.push(item);
});
console.log("After Compare : " + array1);
}
I suggest to use a temporary object for the reference to the id and update if exist or push if not exist.
var parentArray1 = [{ "id": 1, "name": "test", "context": [{ "id": 1.1, "name": "test 1.1" }] }, { "id": 2, "name": "test" }, { "id": 3, "name": "test", "context": [{ "id": 3.1, "name": "test 3.1" }] }, { "id": 4, "name": "test" }],
changedArray = [{ "id": 1, "name": "test1", "context": [{ "id": 1.1, "name": "Changed test 1.1" }] }, { "id": 5, "name": "test5" }];
function insert(array, data) {
function iter(array) {
array.forEach(function (a) {
if (!('id' in a)) {
return;
}
if (o[a.id] !== a) {
o[a.id] = a;
}
Object.keys(a).forEach(function (k) {
Array.isArray(a[k]) && iter(a[k]);
});
});
}
var o = {};
iter(array);
data.forEach(function (a) {
if (o[a.id]) {
Object.keys(a).forEach(function (k) {
o[a.id][k] = a[k];
});
return;
}
array.push(a);
});
}
insert(parentArray1, changedArray);
document.write('<pre>' + JSON.stringify(parentArray1, 0, 4) + '</pre>');
This is what I came up with:
function sameKeys(o1, o2, keys) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!o1.hasOwnProperty(key) || !o2.hasOwnProperty(key))
throw 'compared objects do not have the key ' + key;
if (o1[key] !== o2[key])
return false;
}
return true;
}
function isNothing(o) {
return typeof(o) === 'undefined' || o === null;
}
// this does not work if objects have functions as properties
function clone(o) {
if (isNothing(o))
return o;
return JSON.parse(JSON.stringify(o));
}
function extend(o1, o2, keys) {
if (isNothing(o2))
return;
if (isNothing(o1))
throw ('first parameter cannot be empty');
if (typeof(o1) != 'object' || typeof(o2) != 'object')
throw ('extend only works on objects');
Object.keys(o2).forEach(function (key) {
var newVal = o2[key];
if (o1.hasOwnProperty(key)) {
if (isNothing(newVal)) {
delete o1[key];
} else
if (Array.isArray(newVal)) {
compareArray(o1[key], newVal, keys);
} else {
switch (typeof(newVal)) {
case 'object':
extend(o1[key], newVal, keys);
break;
case 'boolean':
case 'number':
case 'string':
o1[key] = newVal;
break;
default:
throw 'not supported property type: ' + typeof(newVal);
}
}
} else {
o1[key] = clone(newVal);
}
});
}
function removeFromArray(arr, ids, keyArray) {
var indexes = [];
var it1s = arr.forEach(function (it, idx) {
if (sameKeys(ids, it, keyArray)) {
indexes.push(idx);
} else {
Object.keys(it).forEach(function (key) {
var newVal = it[key];
if (Array.isArray(newVal)) {
removeFromArray(it[key], ids, keyArray);
}
});
}
});
if (indexes.length) {
if (indexes.length > 1)
throw 'found multiple possible objects for the same key combination'
arr.splice(indexes[0], 1);
}
}
function compareArray(a1, a2, keyArray) {
a2.forEach(function (it2) {
var it1s = a1.filter(function (it) {
return sameKeys(it2, it, keyArray);
});
var it1;
if (!it1s.length) {
it1 = clone(it2);
a1.push(it1);
} else {
if (it1s.length > 1)
throw 'found multiple possible objects for the same key combination'
it1 = it1s[0];
extend(it1, it2, keyArray);
}
if (it2.removedIds) {
it2.removedIds.forEach(function (ids) {
removeFromArray(a1, ids, keyArray);
});
}
});
}
Use it with compareArray(parentArray1,changedArray,['id']);
Note that it would not work with objects that contain functions. Also, if the arrays would be large, perhaps a better solution is to sort both arrays by key, then always look from the last found object up. That's all I got for now.
Updated it with some concepts from Nina and some clearing of the code.
As I understood it, you only want to add properties. So extend({a: {b: 2}},{a:{c:3}}) will result in {a: {b:2,c:3}}. If this is not what you wanted, let me know.
I also added functionality for removing ids. If any of the objects in the array contains a removedIds array of the form [{id: 4},{id: 5}] then the items with those ids will be removed from the original array.
Slight modification on code, to satisfy your conditions. Try it!
function compareArray(originalArray, destinationArray, propertyArray) {
var newItem = new Array(), processedItem = new Array();
for (var i = 0; i < originalArray.length; i++) {
var sourceElement = originalArray[i];
for (var j = 0; j < destinationArray.length; j++) {
var destinationElement = destinationArray[j];
var isUpdated = false;
if (sourceElement.constructor === Array) {
compareArray(sourceElement, destinationElement, propertyArray);
} else {
/* loop the property name to validate */
propertyArray.map(function(property) {
if (sourceElement[property]) {
if (sourceElement[property] === destinationElement[property]) {
originalArray[i] = _.clone(destinationElement);
isUpdated = true;
return;
} else {
var isAvailable = _.find(newItem, function(item) {
return item[property] === destinationElement[property];
});
if (!isAvailable) {
var isAlreadyProcessed = _.find(processedItem, function(item) {
return item[property] === destinationElement[property];
});
if(!isAlreadyProcessed){
newItem.push(destinationElement);
}
}
}
}
});
}
if (isUpdated === true) {
break;
}
}
processedItem.push(sourceElement);
}
newItem.map(function(item) {
originalArray.push(item);
});
return originalArray;
}

Categories

Resources