I have a nested array of objects like this:
let data = [
{
id: 1,
title: "Abc",
children: [
{
id: 2,
title: "Type 2",
children: [
{
id: 23,
title: "Number 3",
children:[] /* This key needs to be deleted */
}
]
},
]
},
{
id: 167,
title: "Cde",
children:[] /* This key needs to be deleted */
}
]
All I want is to recursively find leaves with no children (currently an empty array) and remove the children property from them.
Here's my code:
normalizeData(data, arr = []) {
return data.map((x) => {
if (Array.isArray(x))
return this.normalizeData(x, arr)
return {
...x,
title: x.name,
children: x.children.length ? [...x.children] : null
}
})
}
You need to use recursion for that:
let data = [{
id: 1,
title: "Abc",
children: [{
id: 2,
title: "Type 2",
children: [{
id: 23,
title: "Number 3",
children: [] /* This key needs to be deleted */
}]
}]
},
{
id: 167,
title: "Cde",
children: [] /* This key needs to be deleted */
}
]
function traverse(obj) {
for (const k in obj) {
if (typeof obj[k] == 'object' && obj[k] !== null) {
if (k === 'children' && !obj[k].length) {
delete obj[k]
} else {
traverse(obj[k])
}
}
}
}
traverse(data)
console.log(data)
Nik's answer is fine (though I don't see the point of accessing the children key like that), but here's a shorter alternative if it can help:
let data = [
{id: 1, title: "Abc", children: [
{id: 2, title: "Type 2", children: [
{id: 23, title: "Number 3", children: []}
]}
]},
{id: 167, title: "Cde", children: []}
];
data.forEach(deleteEmptyChildren = o =>
o.children.length ? o.children.forEach(deleteEmptyChildren) : delete o.children);
console.log(data);
If children is not always there, you can change the main part of the code to:
data.forEach(deleteEmptyChildren = o =>
o.children && o.children.length
? o.children.forEach(deleteEmptyChildren)
: delete o.children);
Simple recursion with forEach is all that is needed.
let data = [{
id: 1,
title: "Abc",
children: [{
id: 2,
title: "Type 2",
children: [{
id: 23,
title: "Number 3",
children: [] /* This key needs to be deleted */
}]
}, ]
},
{
id: 167,
title: "Cde",
children: [] /* This key needs to be deleted */
}
]
const cleanUp = data =>
data.forEach(n =>
n.children.length
? cleanUp(n.children)
: (delete n.children))
cleanUp(data)
console.log(data)
This assumes children is there. If it could be missing than just needs a minor change to the check so it does not error out on the length check. n.children && n.children.length
You can do it like this using recursion.
So here the basic idea is in removeEmptyChild function we check if the children length is non zero or not. so if it is we loop through each element in children array and pass them function again as parameter, if the children length is zero we delete the children key.
let data=[{id:1,title:"Abc",children:[{id:2,title:"Type2",children:[{id:23,title:"Number3",children:[]}]},]},{id:167,title:"Cde",children:[]},{id:1}]
function removeEmptyChild(input){
if( input.children && input.children.length ){
input.children.forEach(e => removeEmptyChild(e) )
} else {
delete input.children
}
return input
}
data.forEach(e=> removeEmptyChild(e))
console.log(data)
Related
I want to access last object inside nested array of objects and change its property value, I don't know its id or any any other property value
const arr = [ {id: 1, comment:'parent 01', parentId:null, reply:true, children:[{id: 11, comment:'child', reply:true, parentId:1, children:[{id: 21, comment:'super child ', reply:true,parentId:11 }] }] }, {id: 2, comment:'parent 02', reply:true, parentId:null } ]
I want to access this below object and change its property value:
{id: 21, comment:'super child ', reply:true, parentId:11 }
// result should be:
{id: 21, comment:'super child ', reply:false, parentId:11 }
Need to do it recursively and when the depth is greater than 0 and it doesn't have any children then need to modify the object(s).
const arr = [{
id: 1,
comment: 'parent 01',
parentId: null,
reply: true,
children: [{
id: 11,
comment: 'child',
reply: true,
parentId: 1,
children: [{
id: 21,
comment: 'super child ',
reply: true,
parentId: 11
}]
}]
}, {
id: 2,
comment: 'parent 02',
reply: true,
parentId: null
}];
const build = (arr, depth) => {
const nodes = [];
arr.forEach((val) => {
if (depth > 0 && !val["children"]) { //Base case
nodes.push({ ...val,
reply: false
});
return;
}
nodes.push(val["children"] ? { ...val,
"children": build(val["children"], depth + 1)
} : val);
});
return nodes;
}
console.log(build(arr, 0));
I am attempting to build a new object from an existing deep nested object. I can't seem to get my mind in recurive mode but I am running into a bit of trouble:
oldObjArr = [{
id:1,
name:"Record1"
},{
id:2,
name:"Record2"
},{
id:3,
name:"Record3",
kids:[{
id: 4,
name: "Child 3-1"
},{
id: 5,
name: "Child 3-2"
}]
}]
buildTreeNodes = (node) => {
let data = []
node.map(record=>{
record["icon"] = "..."
record["color"] = "..."
data.push(record)
record.kids && buildTreeNodes(record.kids)
})
}
let newObjArr = buildTreeNodes(oldObjArr)
This OBVIOUSLY does not work, but I can't figure out what will. The resulting object should look like this:
[{
id:1,
name:"Record1",
icon:"...",
color: "...",
},{
id:2,
name:"Record2",
icon:"...",
color: "...",
},{
id:3,
name:"Record3",
icon:"...",
color: "...",
kids:[{
id: 4,
name: "Child 3-1",
icon:"...",
color: "...",
},{
id: 5,
name: "Child 3-2",
icon:"...",
color: "...",
}]
}]
Thanks for any help.
Robert's answer is correct.
If by chance you also want to not mutate the original object, then you can do something like this.
Also using ES6 features coz why not.
const oldObjArr = [{
id: 1,
name: "Record1"
}, {
id: 2,
name: "Record2"
}, {
id: 3,
name: "Record3",
kids: [{
id: 4,
name: "Child 3-1"
}, {
id: 5,
name: "Child 3-2"
}]
}];
function transformObject(item) {
if (Array.isArray(item.kids))
return {
...item, icon: '...', color: '...',
kids: item.kids.map(transformObject)
};
else
return {...item, icon: '...', color: '...' };
}
const newArray = oldObjArr.map(transformObject);
console.log(newArray);
So you iterate over you array and take each object and then add your props to it. Then you check if kids exist and some check if is array. i use instanceof but like #Heretic Monkey point it can be Array.isArray. What more you can setup type guard on front of function check that array argument is array then this you don't have to check that if kids is type of array.
const oldObjArr = [{
id:1,
name:"Record1"
},{
id:2,
name:"Record2"
},{
id:3,
name:"Record3",
kids:[{
id: 4,
name: "Child 3-1"
},{
id: 5,
name: "Child 3-2"
}]
}]
const addKeys = arr => {
for(const obj of arr){
obj['icon'] = "test"
obj['color'] = "test"
if("kids" in obj && obj.kids instanceof Array){
addKeys(obj.kids);
}
}
}
addKeys(oldObjArr)
console.log(oldObjArr)
V2
const addKeys = arr => {
if(!Array.isArray(arr))
return;
for(const obj of arr){
if(typeof obj !== "object")
continue;
obj['icon'] = "test"
obj['color'] = "test"
if("kids" in obj){
addKeys(obj.kids);
}
}
}
Ok check this out:
buildTreeNodes = (node) => {
let data = node.map(record=>{
record["icon"] = "..."
record["color"] = "..."
if (record.kids) record.kids = buildTreeNodes(record.kids);
return record;
})
return data;
}
let newObjArr = buildTreeNodes(oldObjArr)
console.log(newObjArr)
I think this is what you were after. You have to return record with each iteration of map, and it will add it directly to data array. The recursion within works the same.
All details are commented in demo below
let objArr = [{
id: 1,
name: "Record 1"
}, {
id: 2,
name: "Record 2"
}, {
id: 3,
name: "Record 3",
kids: [{
id: 4,
name: "Child 3-1"
}, {
id: 5,
name: "Child 3-2"
}]
},
/*
An object with a nested object not in an array
*/
{
id: 6,
name: 'Record 6',
kid: {
id: 7,
name: 'Child 6-1'
}
},
/*
An object that's filtered out because it doesn't have 'id' key/property
*/
{
no: 0,
name: null
},
/*
An object that's filtered out because it doesn't have 'id' key/property BUT has a nested object that has 'id'
*/
{
no: 99,
name: 'Member 99',
kid: {
id: 8,
name: 'Scion 99-1'
}
}
];
/*
Pass an object that has the key/value pairs that you want added to other objects
*/
const props = {
icon: '...',
color: '...'
};
/*
Pass...
a single object: {obj} of objArr[]
a single key/property: 'id'
an object that contains the key/value pairs to be added to each object that has key/property of id: {props}
*/
const addProp = (obj, prop, keyVal) => {
/*
Convert {props} object into a 2D array
props = {icon: '...', color: '...'}
~TO~
kvArr = [['icon', '...'], ['color', '...']]
*/
let kvArr = Object.entries(keyVal);
/*
for Each key/value pair of kvArr[][]
assign them to the (obj} if it has ['prop']
as one of it's key/properties
(in this demo it's 'id')
*/
kvArr.forEach(([key, val]) => {
if (obj[prop]) {
obj[key] = val;
}
});
/*
Convert {obj} into a 2D array
obj = {id: 3, name: "Record 3", kids: [{ id: 4, name: "Child 3-1"}, {id: 5, name: "Child 3-2"}]}
~TO~
subArr = [['id', 3], ['name', "Record 3"], ['kids', [{id: 4, name: "Child 3-1"}, {id: 5, name: "Child 3-2"}]]
*/
let subArr = Object.entries(obj);
/*
for Each value of subArr[][] (ie ['v'])
if it's an [Array] call addProp and pass
the {obj} of subArr[][]
*/
/*
if it's an {obj} do the same as above
*/
subArr.forEach(([k, v]) => {
if (Array.isArray(v)) {
v.forEach(subObj => {
addProp(subObj, prop, keyVal);
});
} else if (v instanceof Object) {
addProp(v, prop, keyVal);
}
});
};
// Run addProp() on each {obj} of objArr[]
for (let object of objArr) {
addProp(object, 'id', props);
}
console.log(JSON.stringify(objArr, null, 2));
I'm trying to filter a nested structure, based on a search string.
If the search string is matched in an item, then I want to keep that item in the structure, along with its parents.
If the search string is not found, and the item has no children, it can be discounted.
I've got some code working which uses a recursive array filter to check the children of each item:
const data = {
id: '0.1',
children: [
{
children: [],
id: '1.1'
},
{
id: '1.2',
children: [
{
children: [],
id: '2.1'
},
{
id: '2.2',
children: [
{
id: '3.1',
children: []
},
{
id: '3.2',
children: []
},
{
id: '3.3',
children: []
}
]
},
{
children: [],
id: '2.3'
}
]
}
]
};
const searchString = '3.3';
const filterChildren = (item) => {
if (item.children.length) {
item.children = item.children.filter(filterChildren);
return item.children.length;
}
return item.id.includes(searchString);
};
data.children = data.children.filter(filterChildren);
console.log(data);
/*This outputs:
{
"id": "0.1",
"children": [
{
"id": "1.2",
"children": [
{
"id": "2.2",
"children": [
{
"id": "3.3",
"children": []
}
]
}
]
}
]
}*/
I'm concerned that if my data structure becomes massive, this won't be very efficient.
Can this be achieved in a 'nicer' way, that limits the amount of looping going on? I'm thinking probably using a reducer/transducer or something similarly exciting :)
A nonmutating version with a search for a child.
function find(array, id) {
var child,
result = array.find(o => o.id === id || (child = find(o.children, id)));
return child
? Object.assign({}, result, { children: [child] })
: result;
}
const
data = { id: '0.1', children: [{ children: [], id: '1.1' }, { id: '1.2', children: [{ children: [], id: '2.1' }, { id: '2.2', children: [{ id: '3.1', children: [] }, { id: '3.2', children: [] }, { id: '3.3', children: [] }] }, { children: [], id: '2.3' }] }] },
searchString = '3.3',
result = find([data], searchString);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Having a senior-moment, and struggling to get a recursive method to work correctly in Javascript.
There are similar Q&A's here, though nothing I see that has helped me so far.
That being said, if there is indeed a duplicate, i will remove this question.
Given the following array of objects:
var collection = [
{
id: 1,
name: "Parent 1",
children: [
{ id: 11, name: "Child 1", children: [] },
{ id: 12, name: "Child 2", children: [] }
]
},
{
id: 2,
name: "Parent 2",
children: [
{
id: 20,
name: "Child 1",
children: [
{ id: 21, name: "Grand Child 1", children: [] },
{ id: 22, name: "Grand Child 2", children: [] }
]
}
]
},
{
id: 3,
name: "Parent 3",
children: [
{ id: 31, name: "Child 1", children: [] },
{ id: 32, name: "Child 2", children: [] }
]
},
];
I've gone through a few attempts though my method seems to return early after going through one level only.
My latest attempt is:
Can someone please point me in the right direction.
function findType(col, id) {
for (i = 0; i < col.length; i++) {
if (col[i].id == id) {
return col[i];
}
if (col[i].children.length > 0) {
return findType(col[i].children, id);
}
}
return null;
}
I am trying to find an object where a given id matches, so looking for id 1 should return the whole object with the name Parent 1. If looking for id 31 then the whole object with the id 31 and name Child 1 should be returned.
This would translate into
var t = findType(collection, 1);
or
var t = findType(collection, 31);
Note I would like help with a pure JavaScript solution, and not a plugin or other library. Though they may be more stable, it won't help with the learning curve. Thanks.
You was close, you need a variable to store the temporary result of the nested call of find and if found, then break the loop by returning the found object.
Without, you return on any found children without iterating to the end of the array if not found at the first time.
function findType(col, id) {
var i, temp;
for (i = 0; i < col.length; i++) {
if (col[i].id == id) {
return col[i];
}
if (col[i].children.length > 0) {
temp = findType(col[i].children, id); // store result
if (temp) { // check
return temp; // return result
}
}
}
return null;
}
var collection = [{ id: 1, name: "Parent 1", children: [{ id: 11, name: "Child 1", children: [] }, { id: 12, name: "Child 2", children: [] }] }, { id: 2, name: "Parent 2", children: [{ id: 20, name: "Child 1", children: [{ id: 21, name: "Grand Child 1", children: [] }, { id: 22, name: "Grand Child 2", children: [] }] }] }, { id: 3, name: "Parent 3", children: [{ id: 31, name: "Child 1", children: [] }, { id: 32, name: "Child 2", children: [] }] }];
console.log(findType(collection, 31));
console.log(findType(collection, 1));
.as-console-wrapper { max-height: 100% !important; top: 0; }
const findType = (ar, id) => {
return ar.find(item => {
if (item.id === id) {
return item;
}
return item.children.find(cItem => cItem.id === id)
})
}
I think this suffice your needs
You need to ask for the "found" object
let found = findType(col[i].children, id);
if (found) {
return found;
}
Look at this code snippet
var collection = [{ id: 1, name: "Parent 1", children: [{ id: 11, name: "Child 1", children: [] }, { id: 12, name: "Child 2", children: [] } ] }, { id: 2, name: "Parent 2", children: [{ id: 20, name: "Child 1", children: [{ id: 21, name: "Grand Child 1", children: [] }, { id: 22, name: "Grand Child 2", children: [] } ] }] }, { id: 3, name: "Parent 3", children: [{ id: 31, name: "Child 1", children: [] }, { id: 32, name: "Child 2", children: [] } ] }];
function findType(col, id) {
for (let i = 0; i < col.length; i++) {
if (col[i].id == id) {
return col[i];
}
if (col[i].children.length > 0) {
let found = findType(col[i].children, id);
if (found) {
return found;
}
}
}
return null;
}
var t = findType(collection, 31);
console.log(t);
.as-console-wrapper {
max-height: 100% !important
}
1.
Actually your function findType return the value null for every id parameter at the node
{ id: 11, name: "Child 1", children: [] }
When you hit return, it stops all the recursion.
You have to check the return value from the nested call of findType function.
2.
Your for loop should looke like
for (let i = 0; i < col.length; i++)
instead of
for (i = 0; i < col.length; i++)
Because without the let you share a same variable i in the nested call of the function findType and the value will be changed for the dad calling function.
The function could be :
function findType(col, id) {
for (let i = 0; i < col.length; i++) {
if (col[i].id == id) {
return col[i];
}
var nested = findType(col[i].children, id);
if (nested) return nested;
}
return null;
}
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));