I have a parent object with a child array of objects nest underneath. Each object contains an id key with a unique value. A filter function needs to search the parent object for an id, if it does not equal the given id then recursively search through nested objects for the id until it is found. Once the object with the given key is found the remove and return the updated myObject.
The structure looks as followed:
let myObject = {
key: 1,
name: 'hello',
children: [
{
key: 2,
name: 'world',
children: []
},
{
key: 3,
name: 'hope',
children: [
{
key: 4,
name: 'you',
children: [{
key: 5,
name: 'are',
children: []
}]
},
{
key: 6,
name: 'having',
children: [{
key: 7,
name: 'fun',
children: []
}]
}
]
}
]
}
let given = 4;
if (myObject.key !== given) {
myObject = searchChild(myObject, given)
} else {
myObject = {}
}
function searchChild(parent, given) {
parent.children.map(child => {
return child.children.filter(item => {
if (item.key === given) return item;
else if (item.key !== given
&& child.children.length > 0
&& child.children != undefined) {
searchChild(child.children, given);
}
})
})
}
Currently, I am receiving a type error when running the recursive function.
The output should look like where the keys are updated to the new order in tree:
{
key: 1,
name: 'hello',
children: [
{
key: 2,
name: 'world',
children: []
},
{
key: 3,
name: 'hope',
children: [
{
key: 4,
name: 'having',
children: [{
key: 5,
name: 'fun',
children: []
}]
}
]
}
]
}
Here is function you can call for your object
function searchInChild(parent,key){
parent.children = parent.children.filter((c)=>{
if(key == c.key ){
result = c;
return false;
}
return true;
});
if(result == null){
for(c in parent.children){
searchInChild(parent.children[c],key);
}
}
}
Where, you can simply pass searchInChild(myObject,key) & make result global variable.
You pass child.children but you have to pass child you already iterate through children in the function.
let myObject = {
key: 1,
name: 'hello',
children: [
{
key: 2,
name: 'world',
children: []
},
{
key: 3,
name: 'hope',
children: [
{
key: 4,
name: 'you',
children: [{
key: 5,
name: 'are',
children: []
}]
},
{
key: 6,
name: 'having',
children: [{
key: 7,
name: 'fun',
children: []
}]
}
]
}
]
}
let given = 4;
if (myObject.key !== given) {
myObject = searchChild(myObject, given)
} else {
myObject = {}
}
function searchChild(parent, given) {
if(parent && parent.children) {
parent.children.map(child => {
return child.children.filter(item => {
if (item.key === given) return item;
else if (item.key !== given
&& child.children.length > 0
&& child.children != undefined) {
searchChild(child, given);
}
})
})
}
}
Related
How do I remove an element from a JavaScript object by ID?
For instance I have to remove 004 or 007:
const obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
i am trying to like this, find id but what should be next. It is expected to remove id from the object.
const removeById = (obj = {}, id = '') => {
console.log('obj: ', obj)
const search = obj.children.find(o => o.id === id)
console.log('##search: ', search)
if(search) {
console.log('## parent id: ', obj.id)
...
}
if (obj.children && obj.children.length > 0) {
obj.children.forEach(el => removeById(el, id));
}
}
removeById(obj, '007')
You can use findIndex to get the location in the array.
To remove an element from an array you need to use splice.
You can then loop over the children with some and check the children's children. Using some, you can exit out when you find the id so you do not have to keep looping.
let obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
const removeById = (parentData, removeId) => {
// This is only the parent level!
// If you will never delete the first level parent, this is not needed
if (parentData.id === removeId){
Object.keys(data).forEach(key => delete parentData[key]);
return true;
}
function recursiveFind (children) {
// check if any of the children have the id
const index = children.findIndex(({id}) => id === removeId);
// if we have an index
if (index != -1) {
// remove it
children.splice(index, 1);
// say we found it
return true;
}
// Loop over the chldren check their children
return children.some(child => recursiveFind(child.children));
}
return recursiveFind(parentData.children);
}
removeById(obj,'004');
removeById(obj,'007');
console.log(obj)
We can use a recursive function to do it
let obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
const removeById = (data,id) =>{
if(data.id === id){
delete data.id
delete data.children
return
}
data.children.forEach(d =>{
removeById(d,id)
})
}
removeById(obj,'006')
console.log(obj)
Update: not leave an empty object after removing
let obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
let match = false
const removeById = (data,id) =>{
match = data.some(d => d.id == id)
if(match){
data = data.filter(d => d.id !== id)
}else{
data.forEach(d =>{
d.children = removeById(d.children,id)
})
}
return data
}
let data = [obj]
console.log(removeById(data,'004'))
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've managed to copy the intended object into the intended location (my code is below), but how do I move it? So it will not exist in the original location any more.
So in my example, I want to take the object with id of 14 (very bottom of the object) and move it into the children of the object with id of 3 (towards the top).
I know I need to modify this line: item.children.push(itemToMove) in my moveItem function, but some reason I can't think of it.
Also sorry about the very big/nested object, I wanted to make sure to cover a deeply nested object.
const myObj = [
{
id: 1,
name: '1',
children: [
{
id: 2,
name: '2',
children: [
{
id: 3,
name: '3',
children: []
}
]
},
{
id: 4,
name: '4',
children: [
{
id: 5,
name: '5',
children: [
{
id: 6,
name: '6',
children: [
{
id: 7,
name: '7',
children: []
}
]
}
]
}
]
},
]
},
{
id: 8,
name: '8',
children: [
{
id: 9,
name: '9',
children: [
{
id: 10,
name: '10',
children: []
}
]
},
{
id: 11,
name: '11',
children: [
{
id: 12,
name: '12',
children: [
{
id: 13,
name: '13',
children: [
{
id: 14,
name: '14',
children: []
}
]
}
]
}
]
},
]
}
]
let itemToMove = {
id: 14,
name: '14',
children: []
}
// move item, return updated obj
function moveItem(itemToMove, obj, parentId) {
for (let i=0;i<obj.length;i++) {
const value = obj[i];
const item = search(obj[i], parentId);
if (item) {
item.children.push(itemToMove); // pushed into children, but need to move not duplicate in
break;
}
}
function search(obj, id) {
if (obj.id === id) {
return obj;
}
for (let i=0;i<obj.children.length;i++) {
const possibleResult = search(obj.children[i], id);
if (possibleResult) {
return possibleResult;
}
}
}
return obj;
};
console.log(moveItem(itemToMove, myObj, 3))
I would probably do something like this, taking into account that if the insert fails, you should have some kind of way to re-instate the data. I also used ES6 which is different to your code, but it gives you some kind of idea.
let parent
function removeItem (obj, itemToFind) {
// Loop the object
obj.find((e, index) => {
// If the id's match remove from the parent if it exists otherwise from the object as its at root level
if (e.id === itemToFind.id) {
if (parent) {
parent.children.splice(index, 1)
} else {
obj.splice(index, 1)
}
// break the loop once returned true. Change find to forEach to remove all instances with id if allowing multiples
return true
}
// recurse
else if (e.children && e.children.length > 0) {
parent = e
return removeItem(e.children, itemToFind)
}
})
}
// move item, return updated obj
function moveItem (itemToMove, obj, parentId) {
for (let i = 0; i < obj.length; i++) {
const value = obj[i]
const item = search(obj[i], parentId)
if (item) {
item.children.push(itemToMove) // pushed into children, but need to move not duplicate in
break
}
}
function search (obj, id) {
if (obj.id === id) {
return obj
}
for (let i = 0; i < obj.children.length; i++) {
const possibleResult = search(obj.children[i], id)
if (possibleResult) {
return possibleResult
}
}
}
return obj
};
removeItem(myObj, itemToMove)
moveItem(itemToMove, myObj, 3)
const item = {
id: 'item1',
children: [
{ id: 'item1-1',
children: [
{ id: 'item1-1-1' },
{ id: 'item1-1-2' },
{ id: 'item1-1-3' },
]
},
{ id: 'item1-2',
children: [
{ id: 'item1-2-1' }
]
}
]
}
Like this,
function getLevelOfId(){
...
}
getLevelOfId('item1') =====> return 1
getLevelOfId('item1-2') =====> return 2
getLevelOfId('item1-1-1') =====> return 3
getLevelOfId('item1-1-2') =====> return 3
How to get specific object's depth with JavaScript?
Not use of id string. like ('item1-2').split('-').length Because each object has randomic id. Is there a simple way?
You need to iterate all objects and if found, take one for each level for the recursion depth.
function getLevelOfId(object, id) {
var level;
if (object.id === id) return 1;
object.children && object.children.some(o => level = getLevelOfId(o, id));
return level && level + 1;
}
const item = { id: 'item1', children: [{ id: 'item1-1', children: [{ id: 'item1-1-1' }, { id: 'item1-1-2' }, { id: 'item1-1-3' }] }, { id: 'item1-2', children: [{ id: 'item1-2-1' }] }] };
console.log(getLevelOfId(item, 'item1')); // 1
console.log(getLevelOfId(item, 'item1-2')); // 2
console.log(getLevelOfId(item, 'item1-1-1')); // 3
console.log(getLevelOfId(item, 'item1-1-2')); // 3
console.log(getLevelOfId(item, 'foo')); // undefined
if the structure id & children is fixed, how about search the whole value like "item1-1-1" in the json string:
{"id":"item1","children":[{"id":"item1-1","children":[{"id":"item1-1-1"},{"id":"item1-1-2"},{"id":"item1-1-3"}]},{"id":"item1-2","children":[{"id":"item1-2-1"}]}]}
level = (number of "{") - (number of "}") // before the searched positon of the string
I need to recursively get all children from a nested object.
I already wrote a function that does it (kinda) but I think it can be improved.
How can I make it shorter and cleaner?
I have included the data I'm using for testing as well as the function I wrote that needs improvement.
let data = [{
id: 1,
child: {
id: 2,
child: {
id: 3,
child: {
id: 4,
child: null
}
}
}
},
{
id: 5,
child: {
id: 6,
child: null
}
}
];
// function
for (let cat of data) {
cat.children = getCategoryChild(cat);
console.log(cat.children)
}
function getCategoryChild(cat) {
let t = [];
if (cat.child != null) {
t.push(cat.child);
let y = getCategoryChild(cat.child);
if (y.length > 0) {
for (let i of y) {
t.push(i)
}
}
}
return t;
}
Expected output:
[{id: 1, children: [{id: 2}, {id: 3}, {id: 4}]}, {id: 5, children: [{id: 6}]}]
You could take a recursive approach by checking the actual child property
function convert(array) {
const iter = o => o ? [{ id: o.id }, ...iter(o.child)] : [];
return array.map(({ id, child }) => ({ id, children: iter(child) }));
}
var data = [{ id: 1, child: { id: 2, child: { id: 3, child: { id: 4, child: null } } } }, { id: 5, child: { id: 6, child: null } }];
console.log(convert(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
assuming that each category only ever has one child
edited to adhere to the expected result...
function iterChildren(cat) {
let c = cat, children = [];
while (c.child) {
children.push({id: c.child.id});
c = c.child;
}
return {id: cat.id, children: children};
}
let newData = data.map(iterChildren);
I re-wrote the function.
It filters cats, and only returns an object with id and child_id of each.
let output = [],
data = [{
id: 1,
child: {
id: 2,
child: {
id: 3,
child: {
id: 4,
child: null
}
}
}
},
{
id: 5,
child: {
id: 6,
child: null
}
}
];
function getCategoryChild(cat) {
var t = [{
id: cat.id,
child_id: null
/* HERE you can set, what kind of data should be included to output */
}]
if (cat.child) {
t[0].child_id = cat.child.id
t = t.concat(getCategoryChild(cat.child))
}
return t
}
for (x of data) {
output=output.concat(getCategoryChild(x))
}
console.log(output)
EDIT: I edited my code assuming that one cat can have more children:
let output = [],
data = [{
id: 1,
child: {
id: 2,
child: {
id: 3,
child: {
id: 4,
child: null
}
}
}
},
{
id: 5,
child: {
id: 6,
child: null
}
},
{
id: 7,
child: [
{
id: 8,
child: {
id: 9,
child: null
}
},
{
id: 10,
child: null
},
{
id: 11,
child: null
}
]
},
];
function getCategoryChild(cat) {
var t = [{
id: cat.id,
child_id: []
/* HERE you can set, what kind of data should be included to output */
}]
if (cat.child) {
if (!(cat.child instanceof Array)) {
cat.child = [cat.child]
}
for (var x of cat.child) {
t[0].child_id.push(x.id)
t = t.concat(getCategoryChild(x))
}
}
return t
}
for (x of data) {
output = output.concat(getCategoryChild(x))
}
console.log(output)
data.map(({id,child:c})=>({id,children:[...{*0(){for(;c&&({id}=c);c=c.child)yield{id}}}[0]()]}))