Change object property in nested array - javascript

I have an array:
const array = [
{ id: 1, parent_id: 0, visible: true },
{ id: 2, parent_id: 0, visible: true },
{ id: 3, parent_id: 1, visible: true },
{ id: 4, parent_id: 3, visible: true },
{ id: 5, parent_id: 4, visible: true },
{ id: 6, parent_id: 4, visible: true },
{ id: 7, parent_id: 3, visible: true },
{ id: 8, parent_id: 2, visible: true }
]
I want to create a function with arguments ID and ARRAY, that return new array with VISIBLE = FALSE for this ID and every nested child by PARENT_ID.
My effort is like this
const result = []
const findFitstHandler = (id, arr) => {
let j
for (let i in arr) {
if (arr[i].id === id) {
result.push(arr[i].id)
j = arr[i].id
}
}
findNested(j, arr)
return array.map(item => {
if (result.includes(item.id)) {
return {
...item,
visible: false
}
} else {
return item
}
})
}
const findNested = (id, arr) => {
for (let i in arr) {
if (arr[i].parent_id === id) {
result.push(arr[i].id)
findNested(arr[i].id, arr)
}
}
}
I'm sure there is a more elegant solution. Please, help me

Try with array map method:
const array = [
{ id: 1, parent_id: 0, visible: true },
{ id: 2, parent_id: 0, visible: true },
{ id: 3, parent_id: 1, visible: true },
{ id: 4, parent_id: 3, visible: true },
{ id: 5, parent_id: 4, visible: true },
{ id: 6, parent_id: 4, visible: true },
{ id: 7, parent_id: 3, visible: true },
{ id: 8, parent_id: 2, visible: true }
];
const getNewArray = (id, items) => items.map(item => {
if ([item.id, item.parent_id].includes(id)) {
item.visible = false;
}
return item;
});
console.log(getNewArray(4, array));

I would break apart the recursive code that finds the list of descendants from the code that does the data manipulation. Here's one possibility:
const descendants = (array, root) =>
[
root,
...array .filter (({parent_id}) => parent_id == root)
.flatMap (({id}) => descendants (array, id))
]
const changeBranch = (fn) => (array, root, keys = descendants (array, root)) =>
array .map (element => keys .includes (element .id) ? fn (element) : element)
const makeInvisible = changeBranch (
({visible, ...rest}) => ({...rest, visible: false})
)
const array = [{ id: 1, parent_id: 0, visible: true }, { id: 2, parent_id: 0, visible: true }, { id: 3, parent_id: 1, visible: true }, { id: 4, parent_id: 3, visible: true }, { id: 5, parent_id: 4, visible: true }, { id: 6, parent_id: 4, visible: true }, { id: 7, parent_id: 3, visible: true }, { id: 8, parent_id: 2, visible: true }];
console .log (makeInvisible (array, 4))
console .log (makeInvisible (array, 2))
.as-console-wrapper {min-height: 100% !important; top: 0}
descendants finds the ids of the root id supplied and all the nodes descendant from it inside your array.
changeBranch takes a function to transform a node and returns a function that takes an array and the root id and returns a new array containing either the result of applying that function (when the node is descendant from the root) or the original value (when it isn't.)
makeInvisible is the result of applying to changeBranch a function which sets visible to false. This is the final function you're looking for.
Note that if your list is cyclic and not hierarchical, this will not work. Your stack would likely overflow.

Related

Is there a way to "merge" two object that are inside an array?

I have two arrays that i want two merge, something like this:
const arr1 = [{
id: 1,
isAvailable: true
},
{
id: 2,
isAvailable: true
},
{
id: 4,
isAvailable: true
},
{
id: 6,
isAvailable: false
}
]
const arr2 = [{
id: 1,
isAvailable: false
},
{
id: 2,
isAvailable: false
},
{
id: 6,
isAvailable: false
}
]
The outcome that I'm looking for is somethig like:
const arr3 = [{
id: 1,
isAvailable: false
},
{
id: 2,
isAvailable: false
},
{
id: 4,
isAvailable: true
},
{
id: 6,
isAvailable: false
}
]
I need to update the values of the first array with the values of the second array so I can have a new array with what's truly available and what's not.
You could first transform arr2 into a map object that maps the IDs to their availability, something like { "1": true, "4": false, ... }. And then map arr1 objects into a new array of objects while updating the isAvailable property using the map object:
let map = arr2.reduce((acc, o) => (acc[o.id] = o.isAvailable, acc), {});
let arr3 = arr1.map(o => ({
id: o.id,
isAvailable: map.hasOwnProperty(o.id) ? map[o.id] : o.isAvailable
}));
Each new object will have the same id as the original. Its isAvailable property will either be the value from the map object or its original value depending on whether or not it has an entry in the map object (whether or not it has an object in arr2)
Here is my sample program according to your example. Hope it solves your problem
const arr1 = [{
id: 1,
isAvailable: true
},
{
id: 2,
isAvailable: true
},
{
id: 4,
isAvailable: true
},
{
id: 6,
isAvailable: true
}
]
const arr2 = [{
id: 1,
isAvailable: false
},
{
id: 2,
isAvailable: false
},
{
id: 6,
isAvailable: false
}
]
const arr3 = []
for (let i = 0; i < arr1.length; i++) {
let found = arr2.find(item => item.id === arr1[i].id)
if (found)
arr3.push(found)
else
arr3.push(arr1[i])
}
console.log(arr3)
Output is
[ { id: 1, isAvailable: false },
{ id: 2, isAvailable: false },
{ id: 4, isAvailable: true },
{ id: 6, isAvailable: false } ]
You can filter arr1 based on arr2 and then just merge filtered arr2 with arr1
const arr1 = [{
id: 1, isAvailable: true
},
{
id: 2, isAvailable: true
},
{
id: 4, isAvailable: true
},
{
id: 6, isAvailable: false
}
];
const arr2 = [{
id: 1, isAvailable: false
},
{
id: 2, isAvailable: false
},
{
id: 6, isAvailable: false
}
];
const result = arr1.filter(a => !arr2.some(s => s.id == a.id)).concat(arr2);
console.log(result);
Or using map, some and find methods:
const arr1 = [{
id: 1, isAvailable: true
},
{
id: 2, isAvailable: true
},
{
id: 4, isAvailable: true
},
{
id: 6,
isAvailable: false
}
];
const arr2 = [{
id: 1, isAvailable: false
},
{
id: 2, isAvailable: false
},
{
id: 6, isAvailable: false
}
];
const result = arr1.map(a=> arr2.some(f=> f.id == a.id) ?
arr2.find(f=> f.id == a.id) : a
);
console.log(result);
You can do this with Array.map() by overwriting the value if it exists in the second array.
const newArray = arr1.map(({ id, isAvailable}) => {
const matchingObjects = arr2.filter(({ id: id2 }) => id === id2);
return matchingObjects.length > 0
? { id, isAvailable: matchingObjects[0].isAvailable } // replace the original value
: { id, isAvailable } // do not overwrite this value
})

How to fix node parent_id?

I’m setting up a data structure,and I want to fix node.
var data = [{
id: 1,
parent_id: 321
}, {
id: 2,
parent_id: 1
}, {
id: 3,
parent_id: 1
}, {
id: 4,
parent_id: 5
}, {
id: 5,
parent_id: 4
}];
const makeTree = (items, id, link = 'parent_id') => items.filter(item => item[link] == id).map(item => ({
...item,
children: makeTree(items, item.id)
}));
console.log(makeTree(data));
If the node has a reference at parent_id that is not present in the collection, my code will not return anything. And it is necessary to return that node (no matter what his parent is not there). Wanted result is when my {id: 1, parent_id: 321} has two children {id: 2, parent_id: 1}, {id: 3, parent_id: 1}.
You could take a staged approach by creating first the relation of all nodes, then get the depth of each node and filter by depth to get the largest. At the end, you get a part tree of the largest nodes.
function getDepth({ id, children }, seen = new Set) {
if (seen.has(id)) return 0; // prevent circular references
if (!children) return 1;
seen.add(id);
return children.reduce((c, o) => c + getDepth(o, seen), 1);
}
function getRelations(data) {
var t = {};
data.forEach((o) => {
Object.assign(t[o.id] = t[o.id] || {}, o);
t[o.parent_id] = t[o.parent_id] || {};
t[o.parent_id].children = t[o.parent_id].children || [];
t[o.parent_id].children.push(t[o.id]);
});
return t;
}
var data = [{ id: 1, parent_id: 321 }, { id: 2, parent_id: 1 }, { id: 3, parent_id: 1 }, { id: 4, parent_id: 5 }, { id: 5, parent_id: 4 }],
relations = getRelations(data),
longest = data
.map(({ id }) => [id, getDepth(relations[id])])
.reduce((r, a, i) => {
if (!i || r[0][1] < a[1]) return [a];
if (r[0][1] === a[1]) return r.push(a);
return r;
}, [])
.map(([id]) => relations[id]);
console.log(longest);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Remove matched object from deeply nested array of objects

I have a data tree structure with children:
{ id: 1,
name: "Dog",
parent_id: null,
children: [
{
id: 2,
name: "Food",
parent_id: 1,
children: []
},
{
id: 3,
name: "Water",
parent_id: 1,
children: [
{
id: 4,
name: "Bowl",
parent_id: 3,
children: []
},
{
id: 5,
name: "Oxygen",
parent_id: 3,
children: []
},
{
id: 6,
name: "Hydrogen",
parent_id: 3,
children: []
}
]
}
]
}
This represents a DOM structure that a user could select an item from to delete by clicking the corresponding button in the DOM.
I have a known text title of the selected item for deletion from the DOM set as the variable clickedTitle. I am having trouble finding an algorithm that will allow me to delete the correct object data from the deeply nested tree.
Here is my code:
function askUserForDeleteConfirmation(e) {
const okToDelete = confirm( 'Are you sure you want to delete the item and all of its sub items?' );
if(!okToDelete) {
return;
}
const tree = getTree(); // returns the above data structure
const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
const updatedTree = removeFromTree(tree, tree, clickedTitle);
return updatedTree;
}
function removeFromTree(curNode, newTree, clickedTitle) {
if(curNode.name === clickedTitle) {
// this correctly finds the matched data item to delete but the next lines don't properly delete it... what to do?
const index = curNode.children.findIndex(child => child.name === clickedTitle);
newTree = curNode.children.slice(index, index + 1);
// TODO - what to do here?
}
for(const node of curNode.children) {
removeFromTree(node, newTree, clickedTitle);
}
return newTree;
}
I have tried to use the info from Removing matched object from array of objects using javascript without success.
If you don't mind modifying the parameter tree in-place, this should do the job. Note that it'll return null if you attempt to remove the root.
const tree = { id: 1, name: "Dog", parent_id: null, children: [ { id: 2, name: "Food", parent_id: 1, children: [] }, { id: 3, name: "Water", parent_id: 1, children: [ { id: 4, name: "Bowl", parent_id: 3, children: [] }, { id: 5, name: "Oxygen", parent_id: 3, children: [] }, { id: 6, name: "Hydrogen", parent_id: 3, children: [] } ] } ] };
const removeFromTree = (root, nameToDelete, parent, idx) => {
if (root.name === nameToDelete) {
if (parent) {
parent.children.splice(idx, 1);
}
else return null;
}
for (const [i, e] of root.children.entries()) {
removeFromTree(e, nameToDelete, root, i);
}
return tree;
};
console.log(removeFromTree(tree, "Oxygen"));
Your current code is very much on the right track. However:
newTree = curNode.children.slice(index, index + 1);
highlights a few issues: we need to manipulate the parent's children array to remove curNode instead of curNode's own children array. I pass parent objects and the child index recursively through the calls, saving the trouble of the linear operation findIndex.
Additionally, slicing from index to index + 1 only extracts one element and doesn't modify curNode.children. It's not obvious how to go about using newArray or returning it through the call stack. splice seems like a more appropriate tool for the task at hand: extracting one element in-place.
Note that this function will delete multiple entries matching nameToDelete.
I like #VictorNascimento's answer, but by applying map then filter, each children list would be iterated twice. Here is an alternative with reduce to avoid that:
function removeFromTree(node, name) {
return node.name == name
? undefined
: {
...node,
children: node.children.reduce(
(children, child) => children.concat(removeFromTree (child, name) || []), [])
}
}
In the case you want a way to remove the items in-place, as #ggorlen proposed, I'd recommend the following solution, that is simpler in my opinion:
function removeFromTree(node, name) {
if (node.name == name) {
node = undefined
} else {
node.children.forEach((child, id) => {
if (!removeFromTree(child, name)) node.children.splice(id, 1)
})
}
return node
}
I've built the algorithm as follows:
function omitNodeWithName(tree, name) {
if (tree.name === name) return undefined;
const children = tree.children.map(child => omitNodeWithName(child, name))
.filter(node => !!node);
return {
...tree,
children
}
}
You can use it to return a new tree without the item:
noHydrogen = omitNodeWithName(tree, "Hydrogen")
If it's ok to use Lodash+Deepdash, then:
let cleaned = _.filterDeep([tree],(item)=>item.name!='Hydrogen',{tree:true});
Here is a Codepen
We use object-scan for many data processing tasks. It's powerful once you wrap your head around it. Here is how you could answer your question
// const objectScan = require('object-scan');
const prune = (name, input) => objectScan(['**[*]'], {
rtn: 'bool',
abort: true,
filterFn: ({ value, parent, property }) => {
if (value.name === name) {
parent.splice(property, 1);
return true;
}
return false;
}
})(input);
const obj = { id: 1, name: 'Dog', parent_id: null, children: [{ id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [{ id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] }] }] };
console.log(prune('Oxygen', obj)); // return true iff pruned
// => true
console.log(obj);
// => { id: 1, name: 'Dog', parent_id: null, children: [ { id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [ { id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] } ] } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

How to get parent id in unlimited nested array in JavaScript

It is as the title, but I am facing a problem!
I want to create getParentId(array, id) function.
This function get parent id by child id.
const array = [{
id: 1,
title: 'hello',
children: [{
id: 3,
title: 'hello',
children: [{
id: 4,
title:'hello',
children: [
{ id: 5, title: 'hello'},
{ id: 6, title: 'hello'}
]
},
{
id: 7,
title: 'hello'
}]
}]
},
{
id: 2,
title: 'hello',
children: [
{ id: 8, title: 'hello'}
]
}]
This array may nest indefinitely
Expected Result:
getParentId(array, 3) -> 1
getParentId(array, 5) -> 4
getParentId(array, 6) -> 4
getParentId(array, 8) -> 2
getParentId(array, 2) -> null
I would be grateful if you would send me information.
You could take a recursive approach by iterating the actual array and their children and stop if the id is found.
function getParentId(array, id, parentId = null) {
return array.some(o => {
if (o.id === id) return true;
const temp = getParentId(o.children || [], id, o.id);
if (temp !== null) {
parentId = temp;
return true;
}
})
? parentId
: null;
}
const array = [{ id: 1, title: 'hello', children: [{ id: 3, title: 'hello', children: [{ id: 4, title:'hello', children: [{ id: 5, title: 'hello' }, { id: 6, title: 'hello' }] }, { id: 7, title: 'hello' }] }] }, { id: 2, title: 'hello', children: [{ id: 8, title: 'hello' }] }];
console.log(getParentId(array, 3)); // 1
console.log(getParentId(array, 5)); // 4
console.log(getParentId(array, 6)); // 4
console.log(getParentId(array, 8)); // 2
console.log(getParentId(array, 2)); // null
console.log(getParentId(array, 7)); // 3
console.log(getParentId(array, 4)); // 3
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz's answer is great, but here's a slightly less functional approach (not using Array.some), if you like it better:
const array = [{id: 1, title: 'hello', children: [{id: 3, title: 'hello', children: [{id: 4, title:'hello', children: [{ id: 5, title: 'hello'}, { id: 6, title: 'hello'}]}, {id: 7, title: 'hello'}]}]}, {id: 2, title: 'hello', children: [{ id: 8, title: 'hello'}]}];
function getParentId(array, id, parentId = null) {
// For every entry in the array
for (const entry of array) {
// If the ID matches, return the current parent ID
if (entry.id === id) {
return parentId;
}
// Otherwise, call the same function on its children, passing itself as the parent.
// If there was a match, return it.
if (entry.children && (deeperParentId = getParentId(entry.children, id, entry.id))) {
return deeperParentId;
}
}
// No match was found
return null;
}
console.log(getParentId(array, 3));
console.log(getParentId(array, 5));
console.log(getParentId(array, 6));
console.log(getParentId(array, 8));
console.log(getParentId(array, 2));
Note that I overcommented it, which is not such a good idea when writing actual code; this is just for the answer.
Also, as I mentioned in the comments, please share your attempts next time.

Building tree array of objects from flat array of objects [duplicate]

This question already has answers here:
Build tree array from flat array in javascript
(34 answers)
Closed 2 years ago.
I want to build a tree array from flat array:
Here is the flat array:
nodes = [
{id: 1, pid: 0, name: "kpittu"},
{id: 2, pid: 0, name: "news"},
{id: 3, pid: 0, name: "menu"},
{id: 4, pid: 3, name: "node"},
{id: 5, pid: 4, name: "subnode"},
{id: 6, pid: 1, name: "cace"}
];
NB: id = node id; pid = parent node id.
I want to transform it into this array:
nodes = [{
id: 1,
name: 'kpittu',
childs: [{
id: 6,
name: 'cace'
}]
}, {
id: 2,
name: 'news'
}, {
id: 3,
name: 'menu',
childs: [{
id: 4,
name: 'node',
childs: [{
id: 5,
name: 'subnode'
}]
}]
}];
I tried to use a recursive function to achieve the expected result, but I'm looking for a better approach. Thanks for your response.
You could use a hash table and take id and pid in every loop as connected nodes.
This proposal works with unsorted data as well.
var nodes = [{ id: 6, pid: 1, name: "cace" }, { id: 1, pid: 0, name: "kpittu" }, { id: 2, pid: 0, name: "news" }, { id: 3, pid: 0, name: "menu" }, { id: 4, pid: 3, name: "node" }, { id: 5, pid: 4, name: "subnode" }],
tree = function (data, root) {
var r = [], o = {};
data.forEach(function (a) {
if (o[a.id] && o[a.id].children) {
a.children = o[a.id] && o[a.id].children;
}
o[a.id] = a;
if (a.pid === root) {
r.push(a);
} else {
o[a.pid] = o[a.pid] || {};
o[a.pid].children = o[a.pid].children || [];
o[a.pid].children.push(a);
}
});
return r;
}(nodes, 0);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can also use Map object, introduced in ES6.
let nodes = [
{ id: 1, pid: 0, name: "kpittu" },
{ id: 2, pid: 0, name: "news" },
{ id: 3, pid: 0, name: "menu" },
{ id: 4, pid: 3, name: "node" },
{ id: 5, pid: 4, name: "subnode" },
{ id: 6, pid: 1, name: "cace" }
];
function toTree(arr) {
let arrMap = new Map(arr.map(item => [item.id, item]));
let tree = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (item.pid) {
let parentItem = arrMap.get(item.pid);
if (parentItem) {
let { children } = parentItem;
if (children) {
parentItem.children.push(item);
} else {
parentItem.children = [item];
}
}
} else {
tree.push(item);
}
}
return tree;
}
let tree = toTree(nodes);
console.log(tree);
Iterate with Array#reduce and a helper object:
var nodes = [
{id: 1, pid: 0, name: "kpittu"},
{id: 2, pid: 0, name: "news"},
{id: 3, pid: 0, name: "menu"},
{id: 4, pid: 3, name: "node"},
{id: 5, pid: 4, name: "subnode"},
{id: 6, pid: 1, name: "cace"}
];
const helper = nodes.reduce((h, o) => (h[o.id] = Object.assign({}, o), h), Object.create(null));
const tree = nodes.reduce((t, node) => {
const current = helper[node.id];
if(current.pid === 0) { // if it doesn't have a parent push to root
t.push(current);
} else {
helper[node.pid].children || (helper[node.pid].children = []) // add the children array to the parent, if it doesn't exist
helper[node.pid].children.push(current); // push the current item to the parent children array
}
return t;
}, []);
console.log(tree);

Categories

Resources