Remove object from array inside a recursive function - javascript

I have the following model object:
const model = {
_id: '1',
children: [
{
id: '2',
isCriteria: true
},
{
id: '3',
isCriteria: true
}
]
}
PS: The depth of children is unknown, so I have to use a recursive function to navigate through it.
I want to delete specific objects fro children array based on the array of ids.
So for example if make the following call removeCriteria(model, ['2']), the result should be:
const model = {
_id: '1',
children: [
{
id: '2',
isCriteria: true
}
]
}
I implemented this function as follows:
function removeCriteria(node, criteria, parent = []) {
if (node.isCriteria) {
if (criteria.length && !criteria.includes(node.id)) {
parent = parent.filter(criteria => criteria.id !== node.id);
}
console.log(parent) // here the parents object is correct but it doesn't modify the original object
}
if (node.children)
for (const child of node.children) removeCriteria(child, criteria, node.children);
}

Assigning to parent doesn't assign to the object property where the value came from.
You need to filter node.children and assign back to that property.
function removeCriteria(node, criteria) {
if (criteria.length == 0) {
return;
}
if (node.children) {
node.children = node.children.filter(child => !child.isCriteria || criteria.includes(child.id));
node.children.forEach(child => removeCriteria(child, criteria));
}
}
const model = {
_id: '1',
children: [{
id: '2',
isCriteria: true
},
{
id: '3',
isCriteria: true
}
]
}
removeCriteria(model, ['2']);
console.log(model);

The issue is you're reassigning the variable parent, which doesn't accomplish anything since you're not mutating the array to remove objects, and instead you're assigning it to a newly created array. I would suggest introducing a parentObj reference to the object to which parent belongs, so then you can set parentObj.children to parent and actually mutate the original object's array property:
const model = {
_id: '1',
children: [
{
id: '2',
isCriteria: true
},
{
id: '3',
isCriteria: true
}
]
};
function removeCriteria(node, criteria, parent = [], parentObj = {}) {
if (node.isCriteria) {
if (criteria.length && !criteria.includes(node.id)) {
parent = parent.filter(criteria => criteria.id !== node.id);
parentObj.children = parent;
}
console.log('parent', parent) // here the parents object is correct but it doesn't modify the original object
}
if (node.children)
for (const child of node.children) removeCriteria(child, criteria, node.children, node);
}
removeCriteria(model, ['2']);
console.log(model);

Related

Loop through a nested Object until a certain value is found, Trees

me and my partner have been cracking our heads at this. we have to create a tree, that obviously has "children" below it.
all we need right now, is to loop over an object to find a certain value, if that value is not in that certain object, then go into its child property and look there.
basically what I'm asking is, how can you loop over nested objects until a certain value is found?
would super appreciate the perspective of a more experienced coder on this.
/// this is how one parent with a child tree looks like right now,
essentially if we presume that the child has another child in the children property,
how would we loop into that?
and maybe if that child also has a child, so on and so on...
Tree {
value: 'Parent',
children: [ Tree { value: 'Child', children: [] } ]
}
You can use recursive function to loop through objects:
const Tree = {
value: 'Parent',
children: [
{
value: 'Child1',
children: [
]
},
{
value: 'Child2',
children: [
{
value: 'Child2.1',
children: [
{
value: 'Child2.1.1',
children: [
]
},
{
value: 'Child2.1.2',
children: [
]
},
]
},
]
},
]
}
function findValue(obj, value)
{
if (obj.value == value)
return obj;
let ret = null;
for(let i = 0; i < obj.children.length; i++)
{
ret = findValue(obj.children[i], value);
if (ret)
break;
}
return ret;
}
console.log("Child1", findValue(Tree, "Child1"));
console.log("Child2.1", findValue(Tree, "Child2.1"));
console.log("Child3", findValue(Tree, "Child3"));
You can try this simple solution:
const myTree = {
value: 'Parent',
children: [
{
value: 'Child1',
children: []
},
{
value: 'Child2'
}
]
}
const isValueInTree = (tree, findValue) => {
if (tree.value === findValue) return true;
if (tree.children && tree.children.length !== 0) {
for (const branch of tree.children) {
const valuePresents = isValueInTree(branch, findValue);
if (valuePresents) return true;
}
}
return false;
}
console.log(isValueInTree(myTree, 'Child2'));
console.log(isValueInTree(myTree, 'Child'));
console.log(isValueInTree(myTree, 'Child1'));

How to fix the count variable value in a recursive method using react and javascript?

I have data like below:
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
],
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
},
],
type: 'MAIN',
},
],
type: 'TYPE2',
},
];
I have to find out the count of type: 'MAIN'. these 'MAIN' will be within type: 'TYPE2'
So the expected count is 6.
below is the code,
const ParentComponent = () => {
const findCount = (arr_obj) => {
let count = 0;
const expectedCount = 2;
const loop = (children) => {
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children);
} else if (type === 'MAIN') {
++count;
if (count > expectedCount) return;
}
}
};
loop(children);
return count > expectedCount;
};
const output = findCount(arr_obj);
return (
//some jsx rendering
);
};
The above code works fine, but I want to make a loop (children) function a pure function. I am not sure how to do it.
The problem now is: I define variables outside the loop method.
How can I define everything as arguments to the function? You could move the function outside the component.
I have tried something like below to make it a pure function
const loop = (children, count = 0) => {
if (!children) return;
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children, count + 1);
} else if (type === 'MAIN') {
++count;
if (count > expectedCount) return;
}
}
console.log('count', count); //this is 0 always when i log
return count;
};
const ParentComponent = () => {
const output = React.useMemo(() => {
return loop(arr_obj);
}, [arr_obj]);
console.log('output', output); // output is always false
return (
//some jsx rendering
)
};
Now the problem with above code is that the count is always 0. I am not sure where the problem is.
Your approach is fine: you update count by passing it as a parameter and by returning its updated value. However, the function returns at three spots and you only return count at the third spot. Furthermore, you call loop recursively, but there, you don't use its return value and you should pass count instead of count + 1 as an argument.
You need to make the following changes:
Inside loop, replace return; with return count; two times.
Inside loop, replace loop(children, count + 1); with count = loop(children, count);
Now, if you would remove if (count > expectedCount) return;, you would get 6 as the result.
I'm not sure what exactly you want but I don't think you'd need to make it complicated with recursion. You can simply achieve the same function like this:
const findCount = (arr) => {
const filteredArray = arr
.filter(el => el.type === 'TYPE2')
// removed all elements whose type is not TYPE2
.map(el => el.children)
// replaced all element with element's children array
.flat()
// flattened the array
.filter(el => el.type === 'MAIN');
// removed all elements whose type is not MAIN
return filteredArray.length;
};

Javascript - Change name of all object keys in nested arrays

This is the array i get:
const packages = [
{
id: '641a1690-6c8b-4ada-ae97-8d82cc4fe7a3',
name: 'com.sample',
children: {
id: 'd7384f60-e4ab-4a86-8e2e-0f66cc32f',
name: 'child.computer.com',
children: { id: 'e4ab-4a86-0f66cc32f560', name: 'child.com' }
}
},
{ id: 'd7384f60-e4ab-4a86-8e2e-0f66cc32f560', name: 'computer.com' },
{ id: 'ca7f972e-64ee-4cb0-80b9-1036fac69d32', name: 'java.util' }
];
So, it is an array of objects, and each object can have children, which again have id, name and possibly children (children is optional), and so on, it can be nested X times.
I want to change key names, id to key, name to title and children will remain children. So, my problem is that i don't know how to change keys inside children, i just change the first level and that is all.. It should be like:
{
key: '641a1690-6c8b-4ada-ae97-8d82cc4fe7a3',
title: 'com.sample',
children: {
key: 'd7384f60-e4ab-4a86-8e2e-0f66cc32f',
title: 'child.computer.com',
children: { key: 'e4ab-4a86-0f66cc32f560', title: 'child.com' }
}
}
You can do this by using Recursion.
Check if the value of the [key-value] pair from the Object#entries() call is an object.
If so, call the transformObj function again recursively for that value. Else return the value as is.
And finally convert the array of [key-value] pairs back to an object by using Object#fromEntries:
const packages = [{ id: '641a1690-6c8b-4ada-ae97-8d82cc4fe7a3', name: 'com.sample', children: { id: 'd7384f60-e4ab-4a86-8e2e-0f66cc32f', name: 'child.computer.com', children: { id: 'e4ab-4a86-0f66cc32f560', name: 'child.com' }}}, { id: 'd7384f60-e4ab-4a86-8e2e-0f66cc32f560', name: 'computer.com' }, { id: 'ca7f972e-64ee-4cb0-80b9-1036fac69d32', name: 'java.util' }];
const replacer = { "id": "key", "name" :"title"};
const transformObj = (obj) => {
if(obj && Object.getPrototypeOf(obj) === Object.prototype){
return Object.fromEntries(
Object.entries(obj)
.map(([k, v]) => [replacer[k] || k, transformObj(v)])
);
}
//Base case, if not an Object literal return value as is
return obj;
}
console.log(packages.map(o => transformObj(o)));
You can try to go through every object inside your array and recursively iterate through its keys. Then you can change the keys you want to change and iterate further through the childrens key.
const packages = [{id: '641a1690-6c8b-4ada-ae97-8d82cc4fe7a3',name:'com.sample',children: {id: 'd7384f60-e4ab-4a86-8e2e-0f66cc32f',name: 'child.computer.com',children: { id: 'e4ab-4a86-0f66cc32f560', name: 'child.com' }}},{ id: 'd7384f60-e4ab-4a86-8e2e-0f66cc32f560', name: 'computer.com' },{ id: 'ca7f972e-64ee-4cb0-80b9-1036fac69d32', name: 'java.util' }];
const renameNestedObjects = (obj) => {
Object.keys(obj).forEach((key, index) => {
if (key == "id") {
obj["key"] = obj["id"];
delete obj["id"];
}
if (key == "name") {
obj["title"] = obj["name"];
delete obj["name"];
}
if (key == "children") {
renameNestedObjects(obj["children"]);
}
});
}
console.log(packages);
packages.forEach(obj => { renameNestedObjects(obj); });
console.log(packages);

How do you get results from within JavaScript's deep object?

expect result: ["human", "head", "eye"]
ex.
const data = {
name: "human",
children: [
{
name: "head",
children: [
{
name: "eye"
}
]
},
{
name: "body",
children: [
{
name: "arm"
}
]
}
]
}
const keyword = "eye"
Using the above data and using ffunction to obtain result
expect_result = f(data)
What kind of function should I write?
Thanks.
You could use an iterative and recursive approach by checking the property or the nested children for the wanted value. If found unshift the name to the result set.
function getNames(object, value) {
var names = [];
[object].some(function iter(o) {
if (o.name === value || (o.children || []).some(iter)) {
names.unshift(o.name);
return true;
}
});
return names;
}
var data = { name: "human", children: [{ name: "head", children: [{ name: "eye" }] }, { name: "body", children: [{ name: "arm" }] }] };
console.log(getNames(data, 'eye'));
console.log(getNames(data, 'arm'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try a recursive function
const getData = items => {
let fields = [];
for (const item of items) {
if (item.name) {
fields.push(item.name);
}
if (Array.isArray(item.children)) {
fields.push(...getData(item.children));
}
}
return fields;
}
var result = [] // This is outside the function since it get updated
function f(data) {
result.push(data.name); // Add the only name to the Array
if (data.children) { // just checking to see if object contains key children (staying safe)
for (var i = 0; i < data.children.length; i++) { // we are only looping through the children
f(data.children[i]); // Call this particular function to do the same thing again
}
}
}
Basically this function set the name. Then loop through the no of children and then calls a function to set the name of that child and then loop through its children too. Which happen to be a repercussive function till it finishes in order, all of them

Search through and modify a deeply nested javascript object

I have an object that can be deeply nested with objects, arrays, arrays of objects, and so on.
Every nested object has a sys property which in turn has an id property.
I have a separate list of id values that correspond to objects that I want to remove from the original object. How can I go about recursively looping through the entire object and modify it to no longer include these?
For example, say I have the following data:
let data = {
sys: {
id: '1'
},
items: [
{
sys: {
id: '2'
},
fields: {
title: 'Item title',
sponsor: {
sys: {
id: '3'
},
fields: {
title: 'Sponsor Title'
}
},
actions: [
{
sys: {
id: '4'
},
fields: {
title: 'Google',
url: 'google.com'
}
},
{
sys: {
id: '5'
},
fields: {
title: 'Yahoo',
url: 'yahoo.com'
}
}
]
}
}
]
}
Then I have an array of id's to remove:
const invalidIds = ['3', '5'];
After I run the function, the resulting object should have the property with sys.id of '3' set to null, and the object with sys.id of '5' should simply be removed from its containing array:
// Desired Output:
{
sys: {
id: '1'
},
items: [
{
sys: {
id: '2'
},
fields: {
title: 'Item title',
sponsor: null,
actions: [
{
sys: {
id: '4'
},
fields: {
title: 'Google',
url: 'google.com'
}
}
]
}
}
]
}
With help from this solution, I'm able to recursively search through the object and its various nested arrays:
const process = (key, value) => {
if (typeof value === 'object' && value.sys && value.sys.id && invalidIds.includes(value.sys.id)) {
console.log('found one', value.sys.id);
}
};
const traverse = (obj, func) => {
for (let key in obj) {
func.apply(this, [key, obj[key]]);
if (obj[key] !== null) {
if (typeof obj[key] === 'object') {
traverse(obj[key], func);
} else if (obj[key].constructor === Array) {
obj[key].map(item => {
if (typeof item === 'object') {
traverse(item, func);
}
});
}
}
}
};
traverse(data, process);
However I can't figure out how to properly modify the array. In addition, I'd prefer to create an entirely new object rather than modify the existing one in order to keep things immutable.
Here are the observations that led to my solution:
To create a new object, you need to use return somewhere in your function.
To remove items from array, you need to filter out valid items first, then recursively call traverse on them.
typeof obj[key] === 'object' will return true even for Array, so
next else if block won't ever be hit.
As for implementation, my first step was to create a helper good function to detect invalid objects.
good = (obj) =>{
try{return !(invalidIds.includes(obj.sys.id));}
catch(err){return true;}
}
Now the main traverse -
traverse = (obj) => {
//I assumed when an object doesn't have 'sys' but have 'id', it must be sys obj.
if (obj==null) return null;
if(obj.constructor === Array) return obj.filter(good).map(traverse);
if(obj.sys==undefined) { //this checks if it's sys object.
if(obj.id!=undefined) return obj;
}
for (let key in obj) {
if (key!=0) {
if (good(obj[key])) {obj[key] = traverse(obj[key]);}
else {obj[key] = null;}
}
}
return obj;
};
In case of Array objects, as per point 2, I filtered out valid objects first, then mapped them to traverse. In case of Objects, = operator was used to catch valid sub-objects, returned by recursive call to traverse, instead of simply modifying them.
Note: I hardly know javascript, but took a go at it anyway because this recursive problem is fairly common. So watch out for JS specific issues. Specifically, as outlined in comment, I'm not content with how I checked for 'sys' objects.

Categories

Resources