I have array of objects, each object must have key and title, but children is optional, and it can be nested, i can have children inside of children many times. I want to remove some object by provided key value (for example key 677). I tried with filter but i only remove first level. Also have tried recursion, but not sure if i did it right.
const data = [{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{
key: '677',
title: 'child'
}]
}]
},
{
key: '123',
title: 'tile 111'
},
{
key: '345',
title: 'something'
}
];
const rem = '677';
const del = (el) => {
if (!el.children) {
return el.key !== rem;
} else {
if (el.key !== rem) {
del(el.children);
return el;
}
}
};
const res = data.filter((el) => {
return del(el);
});
console.log(res);
I guess your existing solution is like
const data = [
{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{ key: '677', title: 'child'}]
}]
},
{ key: '123', title: 'tile 111' },
{ key: '345', title: 'something' }
];
function removeByKey(arr, removingKey){
return arr.filter( a => a.key !== removingKey);
}
So it works on the first level but not deeply.
Just change it like that will do the jobs
function removeByKey(arr, removingKey){
return arr.filter( a => a.key !== removingKey).map( e => {
return { ...e, children: removeByKey(e.children || [], removingKey)}
});
}
Little warning, children property will not be set to [] for every item not having any children.
So how it works? Well instead of keeping acceptable items as they are, we make a copy using {...e} that's equivalent to {key:e.key, title:e.title, children:e.children} in this case.
We know force to override the property children with removeByKey(e.children || [], removingKey), so we call the method recursively. Not the function works deeeply.
I would use a recursion approach with findIndex and splice. Using some will allow the code to exit without running through the entire tree.
const data = [{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{
key: '677',
title: 'child'
}]
}]
},
{
key: '123',
title: 'tile 111'
},
{
key: '345',
title: 'something'
}
];
const removeKey = (data, key) => {
// look to see if object exists
const index = data.findIndex(x => x.key === key);
if (index > -1) {
data.splice(index, 1); // remove the object
return true
} else {
// loop over the indexes of the array until we find one with the key
return data.some(x => {
if (x.children) {
return removeKey(x.children, key);
} else {
return false;
}
})
}
}
console.log(removeKey(data, '677'))
console.log(JSON.stringify(data));
You can use some simple recursion to do the trick:
const data = [
{
key: '1',
title: 'title 1',
children: [
{
key: '098',
title: 'hey',
children: [{ key: '677', title: 'child'}]
}
]
},
{ key: '123', title: 'tile 111' },
{ key: '345', title: 'something' }
];
function removeByKey(key, arr) {
// loop through all items of array
for(let i = 0; i < arr.length; i++) {
// if array item has said key, then remove it
if(arr[i].key === key) {
arr.splice(i, 1);
} else if(typeof(arr[i].children) !== "undefined") {
// if object doesn't have desired key but has children, call this function
// on the children array
removeByKey(key, arr[i].children);
}
}
}
removeByKey('098', data);
console.log(data);
This may be a little easier to understand than the other answer provided.
Related
The current code works with no problem. I like to know if there is a cleaner or better way to remove objects from the root of my array if a value is found somewhere in a nested object. I want to remove the object
const baseArray = [
{
title: 'Test folder',
UUID: '54F5E250-1C5F-49CC-A963-5B7FD8884E48',
Children: [
{
title: 'nothing',
UUID: 123,
},
]
},
{
title: 'nothing',
UUID: 123,
},
]
const currArray = baseArray
const removeRootUUID = (baseArray, currArray) => {
const delInRoot = uuidRef => {
baseArray.forEach((obj, i) => {
if (obj.UUID === uuidRef) {
baseArray.splice(i, 1);
}
});
};
for (const obj of currArray) {
if (obj.Children) {
obj.Children.forEach(node => {
delInRoot(node.UUID);
});
removeRootUUID(baseArray, obj.Children);
}
}
};
output I am looking for is below. (because UUID is founded in root and in another nested object)
{
title: 'Test folder',
UUID: '54F5E250-1C5F-49CC-A963-5B7FD8884E48',
Children: [
{
title: 'nothing',
UUID: 123,
},
],
}
This question already has answers here:
Dynamically set property of nested object
(28 answers)
Find by key deep in a nested array
(21 answers)
Closed last year.
Given an array like this, where the maximum depth can be 3 levels and where we don't know at what level the researched item could be:
const data = {
id: '1',
children: [
{
id: '2',
name: 'nameTest',
children: [
{
id: '3'
name: 'deepLevel'
}
]
}
}
how can I add a property to the third level knowing only the value 'deepLevel' ?
we are allowed to use lodash and strongly encouraged to use ES6.
the final dataStructure should be
Given an array like this, where the maximum depth can be of 3 levels:
const data = {
id: '1',
children: [
{
id: '2',
name: 'nameTest',
children: [
{
id: '3'
name: 'deepLevel'
addedProperty: true,
}
]
}
}
An approach was to separate the tasks of finding a nested item by a custom(izable) entry (key-value pair) and assigning additional custom data to the found item.
Thus one e.g. could implement two methods recursivelyFindItemByEntry which is based on self recursion and a simple assignToObjectWithFirstMatchingNestedEntry which assigns provided data to the result of the former function invocation ...
function recursivelyFindItemByEntry(obj, [key, value]) {
let item;
if (!!obj && (typeof obj === 'object')) {
if (
obj.hasOwnProperty(key) &&
(obj[key] === value)
) {
item = obj;
} else if (
obj.hasOwnProperty('children') &&
Array.isArray(obj.children)
) {
obj.children.some(child => {
item = recursivelyFindItemByEntry(child, [key, value]);
return !!item;
});
}
}
return item;
}
function assignToObjectWithFirstMatchingNestedEntry(obj, [key, value], data) {
Object.assign(
recursivelyFindItemByEntry(obj, [key, value]) ?? {},
data ?? {}
);
return obj;
}
const data = {
id: '1',
children: [{
id: '2',
name: 'nameTest',
children: [{
id: '3',
name: 'deepLevel',
}, {
id: '4',
name: 'deepLevel',
}],
}, {
id: '5',
name: 'nameTest',
children: [{
id: '6',
name: 'deepLevel',
}, {
id: '7',
name: 'deepLevelTarget',
// addedProperty: true,
}, {
id: '8',
name: 'deepLevel',
}],
}, {
id: '9',
name: 'nameTest'
}, {
id: '10',
name: 'nameTestTarget'
}, {
id: '11',
name: 'nameTest'
}],
};
console.log(
"recursivelyFindItemByEntry(data, ['name', 'deepLevelTarget']) ...",
recursivelyFindItemByEntry(data, ['name', 'deepLevelTarget'])
);
console.log(
"recursivelyFindItemByEntry(data, ['id', '10']) ...",
recursivelyFindItemByEntry(data, ['id', '10'])
);
console.log('\n');
console.log(
"recursivelyFindItemByEntry(data, ['id', 'foo']) ...",
recursivelyFindItemByEntry(data, ['id', 'foo'])
);
console.log(
"recursivelyFindItemByEntry(data, ['id', '1']) ...",
recursivelyFindItemByEntry(data, ['id', '1'])
);
console.log('\n');
console.log(
"assignToObjectWithFirstMatchingNestedEntry(data, ['name', 'deepLevelTarget']), { addedProperty: true } ...",
assignToObjectWithFirstMatchingNestedEntry(data, ['name', 'deepLevelTarget'], { addedProperty: true })
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
let's say I have an array of objects as below, this array has a list of objects which has a key and document count, the key has hierarchical information as a string separated by ">", the start of the attribute is parent, and then its child and so on.
[
{
key: 'sport',
doc_count: 2
},
{
key: 'sport>competition',
doc_count: 2
},
{
key: 'sport>competition>cricket',
doc_count: 1
},
{
key: 'sport>competition>football',
doc_count: 1
},
{
key: 'movies',
doc_count: 1
}
{
key: 'movies>english',
doc_count: 1
}
]
Can any one suggest me the quickest method to convert this array to this hierarchical data(See Below) in javascript.
[
{
label: 'sport',
parent: null,
doc_count: 2,
children: [
{
label: 'competition',
parent: 'sport',
doc_count: 2,
children: [
{
label: 'cricket',
parent: 'competition',
doc_count: 1,
children: []
},
{
label: 'football',
parent: 'competition',
doc_count: 1,
children: []
}
]
}
]
},
{
label: 'movies',
parent: null,
doc_count: 1,
children: [
{
label: 'english',
parent: 'movies',
doc_count: 1,
children: []
}
]
}
]'
I'm not going to do it for you (not out of malice, just because I'm not 100% sure I understand the question correctly),
but I'll hopefully give you a big point in the right direction.
The problem has something to do with recursion. If you are not aware recursion look like this:
function walk(items){
items.forEach(items => {
if(item.hasChildrenOrSomething){
// notice the function calls itself:
walk(item.children);
}
// Do something with item
});
}
walk(tree);
Here is a way to classify them in a tree structure, later on you can modify the data in it. This is just a step towards your solution.
I'm reducing the data here and calling a recursive function resolveData by passing accumulator and splitted key.
You can understand it better from the code:
var data = [{ key: 'sport', doc_count: 2 }, { key: 'sport>competition', doc_count: 2 }, { key: 'sport>competition>cricket', doc_count: 1 }, { key: 'sport>competition>football', doc_count: 1 }, { key: 'movies', doc_count: 1 }, { key: 'movies>english', doc_count: 1 }];
let resolveData = (acc, keyArray) => {
if (keyArray.length > 1) {
getIndex = acc.findIndex(val => val.key == keyArray[0]);
acc[getIndex].children = acc[getIndex].children || [];
isPresent = acc[getIndex].children.find(val => val.key == keyArray[1]);
if (!isPresent) acc[getIndex].children = [...acc[getIndex].children, { key: keyArray[1], children: [] }];
keyArray.shift();
resolveData(acc[getIndex].children, keyArray); // here we are performing recursion
}
return acc;
};
var tree = data.reduce((acc, elem) => {
keys = elem.key.split('>');
if (keys.length == 1) {
acc.push(elem); // pushing parent element here
} else {
var result = resolveData(acc, keys);
getAccIndex = acc.findIndex((value) => value.key == result[0].key);
acc[getAccIndex] = result[0];
}
return acc;
}, []);
console.log(tree);
I hope this will lead you to further direction. Thanks!
I solved it using this method,
function buildHierarchy(data) {
const categories = data.map((value) => {
const categoryKeys = value.key.split('>');
return {
label: categoryKeys[categoryKeys.length - 1],
parent: (categoryKeys.length > 1) ? categoryKeys[categoryKeys.length - 2] : null,
doc_count: value.doc_count
};
});
// Create list for top-level node(s)
const list = [];
// Cache for found parent index
const categoryMap = {};
categories.forEach((topic: Topic) => {
// No parentId means top level
if (!topic.parent) {
return list.push(topic);
}
// Insert node as child of parent in flat array
let parent = categoryMap[topic.parent];
if (typeof parent !== 'string') {
parent = categories.findIndex((el: Topic) => el.label === topic.parent);
categoryMap[topic.parent] = parent;
}
if (!categories[parent].children) {
return categories[parent].children = [topic];
}
categories[parent].children.push(topic);
});
return list;
}```
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)
I'm creating a function that loops through an array like this:
schema: [{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}, {
And returns a callback with the values of the objects:
eachDeep (array, callback) {
array.forEach(item => {
item.fields.forEach(field => {
callback(field)
})
})
},
As you can see the item.fields.forEach part is harcoded. How can I modify the function so it detects the first property that it's an array and loop through it? (e.g. in this case that property is fields).
To find whether a property of an object is an array or not you can also use this one:
//let item be your object's property
if(typeof item == "object" && item.length > 0){
//do whatever if it is an array
}
You can check if the field is not an array or not, if so loop it, otherwise do something else with it.
var data = [{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}, {
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}];
eachDeep (array, callback) {
array.forEach(item => {
// loop through each property again
item.forEach(prop => {
// if property is an array
if (prop instanceof Array) {
prop.forEach(field => callback(field));
} else {
// property is not an array
// do something else
}
})
})
},
var big_array =
[
{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}
];
for (let item of big_array)
{
for (let key in item)
{
if (Array.isArray(item[key]) )
{
console.log('this is an array do something:', key);
}
}
}
You could check using Array.isArray()
If the goal is to find the first array property you can do the following. Using ES6.
const schema = [{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}]
let firstArr;
schema.forEach(item => {
firstArr = Object.keys(item).filter(k => Array.isArray(item[k]))[0];
})