How to flatten this array :
[
{ID: 0 , TITLE: 'A', children: [{ID: 1, TITLE: 'AA'}]},
{ID: 2 , TITLE: 'B', children: []},
{ID: 3 , TITLE: 'C', children: [{ID: 4, TITLE: 'CC', children:[{ID: 5, TITLE: 'CCC'}]}]}
]
To get something like this :
A
A / AA
B
C / CC / CCC
You could use store the nested item of the actual object and take an iterative and recursive approach with a closure over the path.
For an incrementing ID, you could use either an additional variable for incrementing if a new row is found or iterate at the end the given array and add the index as id.
This proposal uses an additional id variable, because it requires no extra loop.
var array = [{ ID: 0, TITLE: 'A', children: [{ ID: 1, TITLE: 'AA' }] }, { ID: 2, TITLE: 'B', children: [] }, { ID: 3, TITLE: 'C', children: [{ ID: 4, TITLE: 'CC', children: [{ ID: 5, TITLE: 'CCC' }] }] }],
id = 0,
result = array.reduce(function f(p) {
return function (r, o) {
var temp = p.concat(o.TITLE);
r.push({ ID: id++, TITLE: temp.join('/') });
if (o.children) {
o.children.reduce(f(temp), r);
}
return r;
};
}([]), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
There's no need to complicate things here, you just need a function that loop over the array items, get their TITLE and if the item has children, we call the function recursively:
function getLevels(array, parent) {
var results = [];
array.forEach(function(el) {
results.push(!parent ? el["TITLE"] : parent + "/" + el["TITLE"]);
let prefix = parent ? parent + "/" + el["TITLE"] : el["TITLE"];
if (el.children && el.children.length > 0) {
getLevels(el.children, prefix).forEach(function(child) {
results.push(child);
});
}
});
return results;
}
Demo:
var arr = [{
ID: 0,
TITLE: 'A',
children: [{
ID: 1,
TITLE: 'AA'
}]
},
{
ID: 2,
TITLE: 'B',
children: []
},
{
ID: 3,
TITLE: 'C',
children: [{
ID: 4,
TITLE: 'CC',
children: [{
ID: 5,
TITLE: 'CCC'
}]
}]
}
];
function getLevels(array, parent) {
var results = [];
array.forEach(function(el) {
results.push(!parent ? el["TITLE"] : parent + "/" + el["TITLE"]);
let prefix = parent ? parent + "/" + el["TITLE"] : el["TITLE"];
if (el.children && el.children.length > 0) {
getLevels(el.children, prefix).forEach(function(child) {
results.push(child);
});
}
});
return results;
}
console.log(getLevels(arr));
Edit:
This is how to proceed to get the id in the array:
results.push({
"ID": el["ID"],
"TITLE": (!parent ? el["TITLE"] : parent + "/" + el["TITLE"])
});
Demo:
var arr = [{
ID: 0,
TITLE: 'A',
children: [{
ID: 1,
TITLE: 'AA'
}]
},
{
ID: 2,
TITLE: 'B',
children: []
},
{
ID: 3,
TITLE: 'C',
children: [{
ID: 4,
TITLE: 'CC',
children: [{
ID: 5,
TITLE: 'CCC'
}]
}]
}
];
function getLevels(array, parent) {
var results = [];
array.forEach(function(el) {
results.push({
"ID": el["ID"],
"TITLE": (!parent ? el["TITLE"] : parent + "/" + el["TITLE"])
});
let prefix = parent ? parent + "/" + el["TITLE"] : el["TITLE"];
if (el.children && el.children.length > 0) {
getLevels(el.children, prefix).forEach(function(child) {
results.push(child);
});
}
});
return results;
}
console.log(getLevels(arr));
Did you want something like this?
function merge(input, flat_array) {
if (!flat_array) {
// this is the initial recursive call, make the input
// (an array) have the same { child:[] } structure
// as children calls will end up having
flat_array = [];
input = { children: input };
} else {
// since the parent call is just a plain array it doesnt
// have a title so dont bother inserting anything
flat_array.push(input.TITLE);
}
for (let i in input.children) {
// recursively merge the children in into the flat array
// too
merge(input.children[i], flat_array);
}
return flat_array;
}
merge(x);
produces:
["A", "AA", "B", "C", "CC", "CCC"]
Related
I have an object with nested objects. In this object, each objects having two or more sub-objects. I want to get together all sub-objects into an array of data. How to do with JavaScript?
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
The output I want is this:
[{
id: 1,
title: "a",
level: 2,
__parent: null
},
{
id: 2,
title: "b",
level: 1,
__parent: null
},
{
id: 3,
title: "c",
level: 0,
__parent:null
}]
Case 1: Using Recursion
You can make recursive function like this:
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
const result = [];
function recursiveOuput(data){
let tempObj = {};
Object.keys(data).map((key, index) => {
if(typeof data[key] !== 'object'){
tempObj[key] = data[key];
if(!Object.keys(data).includes('__parent') && (index === Object.keys(data).length -1)){
tempObj['__parent'] = null;
result.push(tempObj);
}
}else{
tempObj['__parent'] = null;
result.push(tempObj);
recursiveOuput(data[key]);
}
})
return result;
};
console.log(recursiveOuput(category));
Case 2: Using While loop
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
const result = [];
let parent = category;
while (parent) {
result.push({
id: parent.id,
level: parent.level,
title: parent.title,
__parent: null
})
parent = parent.__parent
};
console.log(result);
It Be some like this :
const myArray = []
const category = ...;
function foo(obj){
myArray.push({
title:obj.title,
....
})
if (obj._parent)
foo(obj._parent)
}
foo(category)
You essentially want to get the list of ancestors.
const ancestors = []
var parent = category
while (parent) {
ancestors.push({
id: parent.id,
level: parent.level,
__parent: null
})
parent = parent.__parent
}
use recursion to extract objects:
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
function extract(obj,arr=[]){
arr.push({...obj,__parent:null})
if(!obj.__parent){
return arr
}
return extract(obj.__parent,arr)
}
let result = extract(category)
console.log(result)
Using Spread/Rest Operator
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
const {__parent, ...firstObject} = category;
result = [
firstObject,
{...category['__parent'], '__parent': null },
{...category['__parent']['__parent'], '__parent': null},
];
console.log(result);
The nested array looks like this:
var arr = [{
id: 2,
name: 'a',
children: []
}, {
id: 5,
name: 'b',
children: [{
id: 14,
name: 'b2'
}]
}, {
id: 15,
name: 'd',
children: []
}];
How can I make a list of ancestor elements, from any given element?
For example, if given element has id: 14 the list should return only the parent:
[{
id: 5,
name: 'b',
children: [...]
}]
I'm looking to replicate a "breadcrumb" navigation
You could handover an object which is the parent and search recursive for the wanted id.
function getParent(object, id) {
var result;
(object.children || []).some(o => result = o.id === id ? object : getParent(o, id));
return result;
}
var array = [{ id: 2, name: 'a', children: [] }, { id: 5, name: 'b', children: [{ id: 14, name: 'b2' }] }, { id: 15, name: 'd', children: [] }];
console.log(getParent({ children: array }, 14));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you like to hand over the array, you could take a nested approach with recursive function.
function getParent(children, id) {
function iter(object) {
var result;
(object.children || []).some(o => result = o.id === id ? object : iter(o));
return result;
}
return iter({ children });
}
var array = [{ id: 2, name: 'a', children: [] }, { id: 5, name: 'b', children: [{ id: 14, name: 'b2' }] }, { id: 15, name: 'd', children: [] }];
console.log(getParent(array, 14));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If we can assume that only two levels exist (parents and children, not children of children) then the following function findAncestor() does what you need. It iterates over all parent elements and checks if they have a child with the relevant ID.
function findAncestor(id) {
for (let i = 0; i < arr.length; i++) {
let obj = arr[i];
if (obj.hasOwnProperty('children')) {
if (obj.children.length > 0) {
for (let j = 0; j < obj.children.length; j++) {
if (obj.children[j].id === id) {
return obj;
}
}
}
}
}
return null;
}
console.info(findAncestor(14));
If you need to handle the case that a child with same ID can occur in several parents, you should use a result array and add all found results to it before returning it in the end.
You can try this way,
var arr = [{
id: 2,
name: 'a',
children: []
}, {
id: 5,
name: 'b',
children: [{
id: 14,
name: 'b2'
}]
}, {
id: 15,
name: 'd',
children: []
}];
function getAncestor(obj,id,ancestor){
if(obj.id===id){
console.log(ancestor);
}
else
if(obj&& obj.children && obj.children.length)
obj.children.forEach(item=>this.getAncestor(item,id,obj));
}
arr.forEach(o=>getAncestor(o,14,{}));
The Depth First Search Algorithm - get parent node is:
function getParentNodeByKey(obj, targetId, paramKey) {
if (obj.children) {
if (obj.children.some(ch => ch[paramKey] === targetId))
return obj;
else {
for (let item of obj.children) {
let check = this.getParentNodeByKey(item, targetId, paramKey)
if (check) {
return check;
}
}
}
}
return null
}
and code to test:
var arr = [{
id: 2,
name: 'a',
children: []
}, {
id: 5,
name: 'b',
children: [{
id: 14,
name: 'b2'
}]
}, {
id: 15,
name: 'd',
children: []
}];
let parentElement;
const valueToFind = 14;
const desiredkey = 'id';
for (let i = 0; i < arr.length; i++) {
parentElement = getParentNodeByKey(arr[i], valueToFind, desiredkey);
if(parentElement)
break;
}
console.log(`The parent element is:`, parentElement);
I have been trying to delete an element with an ID in nested array.
I am not sure how to use filter() with nested arrays.
I want to delete the {id: 111,name: "A"} object only.
Here is my code:
var array = [{
id: 1,
list: [{
id: 123,
name: "Dartanan"
}, {
id: 456,
name: "Athos"
}, {
id: 789,
name: "Porthos"
}]
}, {
id: 2,
list: [{
id: 111,
name: "A"
}, {
id: 222,
name: "B"
}]
}]
var temp = array
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array[i].list.length; j++) {
temp = temp.filter(function(item) {
return item.list[j].id !== 123
})
}
}
array = temp
You can use the function forEach and execute the function filter for every array list.
var array = [{ id: 1, list: [{ id: 123, name: "Dartanan" }, { id: 456, name: "Athos" }, { id: 789, name: "Porthos" }] }, { id: 2, list: [{ id: 111, name: "A" }, { id: 222, name: "B" }] }];
array.forEach(o => (o.list = o.list.filter(l => l.id != 111)));
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
To remain the data immutable, use the function map:
var array = [{ id: 1, list: [{ id: 123, name: "Dartanan" }, { id: 456, name: "Athos" }, { id: 789, name: "Porthos" }] }, { id: 2, list: [{ id: 111, name: "A" }, { id: 222, name: "B" }] }],
result = array.map(o => ({...o, list: o.list.filter(l => l.id != 111)}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could create a new array which contains elements with filtered list property.
const result = array.map(element => (
{
...element,
list: element.list.filter(l => l.id !== 111)
}
));
You can use Object.assign if the runtime you are running this code on does not support spread operator.
Array.filter acts on elements:
var myArray = [{something: 1, list: [1,2,3]}, {something: 2, list: [3,4,5]}]
var filtered = myArray.filter(function(element) {
return element.something === 1;
// true = keep element, false = discard it
})
console.log(filtered); // logs [{something: 1, list: [1,2,3]}]
You can use it like this:
var array = [{
id: 1,
list: [{
id: 123,
name: "Dartanan"
}, {
id: 456,
name: "Athos"
}, {
id: 789,
name: "Porthos"
}]
}, {
id: 2,
list: [{
id: 111,
name: "A"
}, {
id: 222,
name: "B"
}]
}]
for (var i = 0; i < array.length; ++i) {
var element = array[i]
// Filter the list
element.list = element.list.filter(function(listItem) {
return listItem.id !== 111 && listItem.name !== 'A';
})
}
console.log(array)
I have nested tree object I would like filter through without losing structure
var items = [
{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}]
}]
}
];
so for example if id == 2 remove object with id 2 and his children
if id == 3 only remove object with id 3
this's just apiece of object to make question clean but the object it self contains more and more :)
using vanilla javascript, _lodash or Angular2 it's okay
thank you
You can create recursive function using filter() and also continue filtering children if value is Array.
var items = [{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}, ]
}]
}];
function filterData(data, id) {
var r = data.filter(function(o) {
Object.keys(o).forEach(function(e) {
if (Array.isArray(o[e])) o[e] = filterData(o[e], id);
})
return o.id != id
})
return r;
}
console.log(filterData(items, 3))
console.log(filterData(items, 2))
Update: As Nina said if you know that children is property with array you don't need to loop keys you can directly target children property.
var items = [{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}, ]
}]
}];
const filterData = (data, id) => data.filter(o => {
if (o.children) o.children = filterData(o.children, id);
return o.id != id
})
console.log(JSON.stringify(filterData(items, 3), 0, 2))
console.log(JSON.stringify(filterData(items, 2), 0, 2))
If it's ok for your case to use Lodash+Deepdash, then:
let filtered = _.filterDeep(items,(i)=>i.id!=3,{tree:true});
Here is a demo Codepen
You could use an iterative approach with Array#some and call the callback iter recursive for the children. I found, splice.
function deleteItem(id) {
items.some(function iter(a, i, aa) {
if (a.id === id) {
aa.splice(i, 1);
return true;
}
return a.children.some(iter);
});
}
var items = [{ name: "a1", id: 1, children: [{ name: "a2", id: 2, children: [{ name: "a3", id: 3 }] }] }];
console.log(items);
deleteItem(3);
console.log(items);
deleteItem(2);
console.log(items);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use recursive function:
var items = [
{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3,
children: [{
name: "a4",
id: 4,
}]
}]
}]
}
];
function filterId(items, id) {
var len = items.length;
while (len--) {
if (items[len].id === id) {
items.splice(len, 1);
break;
} else {
filterId(items[len].children, id);
}
}
return items;
}
// filtering out the item with 'id' = 4
console.log(filterId(items, 4));
// filtering out the item with 'id' = 2
console.log(filterId(items, 2));
I got the following array:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
}
}
]
}
];
The objects directly in the array are the groups. The 1: is the first language, 2: is second etc. The id is stored in every language object (due to the database I'm using). The children array is built the same way as the 'arr' array.
Example of multiple children:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
},
children: [
{
1: {
id: 3,
title: 'test3',
},
children: []
}
]
}
]
}
];
Now I need to delete items from this array. You can have unlimited children (I mean, children can have children who can have children etc.). I have a function which needs an ID parameter sent. My idea is to get the right object where the ID of language 1 is the id parameter. I got this:
function deleteFromArray(id)
{
var recursiveFunction = function (array)
{
for (var i = 0; i < array.length; i++)
{
var item = array[i];
if (item && Number(item[1].ID) === id)
{
delete item;
}
else if (item && Number(item[1].ID) !== id)
{
recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
}
However, I'm deleting the local variable item except for the item in the array. I don't know how I would fix this problem. I've been looking all over the internet but haven't found anything.
This proposal features a function for recursive call and Array.prototype.some() for the iteration and short circuit if the id is found. Then the array is with Array.prototype.splice() spliced.
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function splice(array, id) {
return array.some(function (a, i) {
if (a['1'].id === id) {
array.splice(i, 1)
return true;
}
if (Array.isArray(a.children)) {
return splice(a.children, id);
}
});
}
splice(arr, 2);
document.write('<pre>' + JSON.stringify(arr, 0, 4) + '</pre>');
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function deleteFromArray(id) {
function recursiveFunction(arr) {
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
if (item && Number(item[1].id) === id) {
arr.splice(i, 1);
} else if (item && Number(item[1].id) !== id) {
item.children && recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
};
deleteFromArray(2);
document.getElementById("output").innerHTML = JSON.stringify(arr, 0, 4);
<pre id="output"></pre>
jsfiddle: https://jsfiddle.net/x7mv5h4j/2/
deleteFromArray(2) will make children empty and deleteFromArray(1) will make arr empty itself.