Getting parent names from a nested array in javascript - javascript

I have the following structure:
{
id: 15
name: "Power",
childCategories: [],
parent: {
id: 10,
name: "Cables",
parent: null,
stockItems: []
}
}
Ideally, what I would like to end up with is the following String structure:
Cables ⟶ Power
But being new to Javascript, I'm not sure how I would achieve this. I'm assuming it would be through recursion.

Looks like you want to "reverse" the way your data is structured ("children -> parents" to "parents -> children").
Your assumption about recursive function is right: in this example I tried to add more data, according to the description given.
const arr = [
{
childCategories: [],
id: 15,
name: "Power",
parent: {
id: 10,
name: "Cables",
parent: null,
stockItems: [],
}
},
{
childCategories: [],
id: 9,
name: "Gas",
parent: {
id: 20,
name: "Pipes",
parent: {
id: 33,
name: "Metal",
parent: null,
stockItems: [],
},
stockItems: [],
}
}
]
const res = []
for (const o of arr) {
recursiveFunct(o)
}
function recursiveFunct(o) {
if (o.parent) {
let parent = o.parent
delete o.parent
parent.children = o
recursiveFunct(parent)
} else {
res.push(o)
}
}
console.log(res)

For each object passed into the recursive function push its name into into an array (result).
If the object has a parent property that isn't null pass the parent object back into the function along with result.
If the parent is null reverse the array and join it up with a "⟶".
const obj = {
id: 153,
name: "Gold plated",
childCategories: [],
parent: {
id: 15,
name: "Power",
childCategories: [],
parent: {
id: 10,
name: "Cables",
parent: null,
stockItems: []
}
}
};
// Pass in the object, and initialise `result`
function getString(obj, result = []) {
// Push each name into the array
result.push(obj.name);
// If the parent is `null` return the joined array
// otherwise pass the parent object back into the
// function along with the result array
if (!obj.parent) {
return result.reverse().join(' ⟶ ');
} else {
return getString(obj.parent, result);
}
}
console.log(getString(obj));

Related

Advanced filtering nested elements in TypeScript/JavaScript

Given the following structure and data:
interface GrandChild {
id: number,
values: Array<string>,
}
interface Child {
id: number,
subItems: Array<GrandChild>
}
interface Foo {
items: Array<Child>
}
const data: Foo = {
items: [
{ id: 1, subItems: [ { id: 10, values: ['10', '100'] }, { id: 11, values: ['11', '110', '1100'] } ] },
{ id: 2, subItems: [ { id: 20, values: ['REMOVE', 'REMOVE'] }, { id: 21, values: ['REMOVE'] } ] },
{ id: 3, subItems: [ { id: 30, values: ['REMOVE'] }, { id: 31, values: ['REMOVE'] }, { id: 32, values: ['REMOVE', '32'] } ] },
]
};
How can I use the Array's methods (filter, map, some, etc.) to achieve the following result?
const expected: Foo = {
items: [
{ id: 1, subItems: [ { id: 10, values: ['10', '100'] }, { id: 11, values: ['11', '110', '1100'] } ] },
{ id: 3, subItems: [ { id: 32, values: ['32'] } ] },
]
}
So far, I filtered the resulting data, removing the undesired elements, as following:
const filteredData: Foo = {
...data,
items: data.items.map(item => ({
...item,
subItems: item.subItems.map(subItem => ({
...subItem,
values: subItem.values.filter(value => value !== 'REMOVE')
}))
}))
}
Resulting:
{
items: [
{ id: 1, subItems: [ { id: 10, values: ['10', '100'] }, { id: 11, values: ['11', '110', '1100'] } ] },
{ id: 2, subItems: [ { id: 20, values: [] }, { id: 21, values: [] } ] },
{ id: 3, subItems: [ { id: 30, values: [] }, { id: 31, values: [] }, { id: 32, values: ['32'] } ] },
]
};
But, I cannot figure a way out to remove the empty subItems elements without looping through the result.
You can check online the above code here.
If you really want to do it just with filter and map, add a filter after each of your maps to remove subItems that have an empty values array and to remove items that have an empty subItems array:
const filteredData = {
...data,
items: data.items
.map((item) => ({
...item,
subItems: item.subItems
.map((subItem) => ({
...subItem,
values: subItem.values.filter((value) => value !== "REMOVE"),
}))
.filter(({ values }) => values.length > 0), // ***
}))
.filter(({subItems}) => subItems.length > 0), // ***
};
But:
When I have map followed by filter, I always ask myself if the data is large enough that I should avoid making multiple passes through it.
When I'm doing lots of nesting of map calls and such, I always ask myself if it would be clearer when reading the code later to use simpler, smaller loops.
Here's what you might do if answering "yes" to either or both of those questions:
const filteredData: Foo = {
...data,
items: [],
};
for (const item of data.items) {
const subItems: Array<GrandChild> = [];
for (const subItem of item.subItems) {
const values = subItem.values.filter((value) => value !== "REMOVE");
if (values.length) {
subItems.push({
...subItem,
values,
});
}
}
if (subItems.length > 0) {
filteredData.items.push({
...item,
subItems,
});
}
}

Create a tree from a list of strings - javascript

I have an array of objects and I want to create a view tree. But I have a problem with creating. My name Test1/Test2 and Test1/Test2/Test3 (id 147) was missed in final tree.
My snippet:
let arr = [{id: 145, name: "Test1/Test2", public: false},
{id: 146, name: "Test1/Test2/Test3", public: false},
{id: 147, name: "Test1/Test2/Test3", public: false},
{id: 148, name: "Test1/Test2/Test4", public: false}];
let result = [];
let level = { result };
arr.forEach((path) => {
path.name.split("/").reduce((r, name, i, a) => {
if (!r[name]) {
r[name] = { result: [], id: path.id };
r.result.push({ name, children: r[name].result });
}
return r[name];
}, level);
});
console.log(result)
Expected result:
[
{
name: "Test1",
children: [
{
name: "Test2",
children: [],
id: 145
},
{
name: "Test2",
children: [
{
name: "Test3",
children: [],
id: 146
},
{
name: "Test3",
children: [],
id: 147
},
{
name: "Test4",
children: [],
id: 148
},
],
},
],
},
];
If I understand well, the number of leaves in your tree should equal the number entries in the input array. So a leaf would never get any children. This is what made you give "Test1" two children, even though all paths have "Test2" as the next part: one child for a leaf, and another functioning as internal node.
By consequence, leaves don't really need a children property, as that children array would always remain empty.
It is clear that the last element of a path needs to be processed a bit differently. That part should always result in the creation of a new node in the tree. The other parts can reuse a (non-leaf) node, if one is available.
This leads to the following change in your code:
let arr = [
{id: 145, name: "Test1/Test2", public: false},
{id: 146, name: "Test1/Test2/Test3", public: false},
{id: 147, name: "Test1/Test2/Test3", public: false},
{id: 148, name: "Test1/Test2/Test4", public: false}
];
let result = [];
let level = { result };
arr.forEach(({name, id}) => { // destructure
let parts = name.split("/");
name = parts.pop(); // don't pass the last part through reducer
parts.reduce((r, name, i) => {
if (!r[name]) {
r[name] = { result: [] };
r.result.push({ name, children: r[name].result });
}
return r[name];
}, level).result.push({ name, id }); // add last part here
});
console.log(result);

How to recursively transform an array of nested objects into array of flat objects?

I have the following array of deeply nested objects:
const data = [
{
name: "foo",
children:[
{
count: 1,
name: "A"
},
{
count: 2,
name: "B"
}
]
},
{
name: "bar",
children: [
{
count: 3,
name: "C",
children: [
{
count: 4,
name: "D"
}
]
}
]
}
]
The way I'd like to transform this would be such as:
const expectedStructure = [
{
count: 1,
name: "A",
label: "foo = A"
},
{
count: 2,
name: "B",
label: "foo = B"
},
{
count: 3,
name: "C",
label: "bar = C"
},
{
count: 4,
name: "D",
label: "bar = D"
}
]
I created recursive function that transforms nested array into array of flat objects.
Here's my code:
function getChildren(array, result=[]) {
array.forEach(({children, ...rest}) => {
result.push(rest);
if(children) {
getChildren(children, result);
}
});
return result;
}
And here's output I get:
[ { name: 'foo' },
{ count: 1, name: 'A' },
{ count: 2, name: 'B' },
{ name: 'bar' },
{ count: 3, name: 'C' },
{ count: 4, name: 'D' } ]
The problem is that I need to add label field to every object in my output array, and I can't find a solution without iterating multiple times through the final array to make desired transformation. How to properly insert label field without hugely augmenting complexity of the function?
Check each iteration whether the current item is a "parent" item, and reassign label if it is.
const data = [{name:"foo",children:[{count:1,name:"A"},{count:2,name:"B"}]},{name:"bar",children:[{count:3,name:"C",children:[{count:4,name:"D"}]}]}];
function getChildren(array, result = [], label = "") {
array.forEach(({ children, name, count }) => {
if (!label || name[1]) {
label = `${name} = `;
}
if (count) {
result.push({ count, name, label: label + name });
}
if (children) {
getChildren(children, result, label);
}
});
return result;
}
const res = getChildren(data);
console.log(res);
You can use a different function for the nested levels, so you can pass the top-level name properties down through all those recursion levels.
function getTopChildren(array, result = []) {
array.forEach(({
name,
children
}) => {
if (children) {
getChildren(children, name, result);
}
});
return result;
}
function getChildren(array, name, result) {
array.forEach(({
children,
...rest
}) => {
rest.label = `${name} = ${rest.name}`;
result.push(rest);
if (children) {
getChildren(children, name, result);
}
});
}
const data = [{
name: "foo",
children: [{
count: 1,
name: "A"
},
{
count: 2,
name: "B"
}
]
},
{
name: "bar",
children: [{
count: 3,
name: "C",
children: [{
count: 4,
name: "D"
}]
}]
}
]
console.log(getTopChildren(data));
You can also do this recursively with flatMap based on whether or not a parent has been passed into the recursive call :
const data = [{
name: "foo",
children: [{
count: 1,
name: "A"
},
{
count: 2,
name: "B"
}
]
},
{
name: "bar",
children: [{
count: 3,
name: "C",
children: [{
count: 4,
name: "D"
}]
}]
}
];
function flatten(arr, parent = null) {
return parent
? arr.flatMap(({name, count, children}) => [
{name, count, label: `${parent} = ${name}`},
...flatten(children || [], parent)
])
: arr.flatMap(({name, children}) => flatten(children || [], name));
}
console.log(flatten(data));
Sometimes it's a little easier to reason about the code and write it clearly using generators. You can yield* from the recursive calls:
const data = [{name: "foo",children:[{count: 1,name: "A"},{ count: 2,name: "B"}]},{name: "bar",children: [{count: 3,name: "C",children: [{count: 4,name: "D"}]}]}]
function* flat(input, n){
if (!input) return
if (Array.isArray(input)) {
for (let item of input)
yield* flat(item, n)
}
let _name = n || input.name
if ('count' in input) {
yield { count:input.count, name:input.name, label:`${_name} = ${input.name}`}
}
yield* flat(input.children, _name)
}
let g = [...flat(data)]
console.log(g)
The function returns a generator, so you need to spread it into a list [...flat(data)] if you want a list or iterate over it if you don't need to store the list.

How to create a function to retrieve all children's id from a nested object

I want to retrieve all child ids of a specific group, which can be deeply nested or not.
Here is a sample json:
[
{
id: 1,
name: 'Desjardins Group 1',
children: [
{ id: 2, name: 'Analysts', children: [] },
{ id: 3, name: 'Administration', children: [] }
]
},
{
id: 4,
name: 'Desjardins Group 2',
children: [
{ id: 5, name: 'Consultants1', children: [] },
{
id: 6,
name: 'Consultant2',
children: [
{
id: 7, name: 'Interns', children: [
{ id: 8, name: 'subInterns1', children: [] },
{ id: 9, name: 'subInterns2', children: [] },
{ id: 10, name: 'subInterns3', children: [] }
]
}
]
}
]
}
]
I'm trying to make a function that takes an id has a parameter, and return all child ids.
Ex: getChildGroups(6) would return 7, 8, 9 and 10.
I guess recursive function and filters are the way to go, but i can't find a proper example.
Here's a simplified version of Johann Bauer's answer.
The first function just finds the first node that matches the given ID, with no need for any accumulation of data:
function findNode(data, id) {
if (!Array.isArray(data)) return;
for (let entry of data) {
if (entry.id === id) {
return entry;
} else {
const node = findNode(entry.children, id);
if (node) {
return node;
}
}
}
}
This second function just gets the child IDs, storing them in the passed array, without any intermediate arrays being created:
function getChildIds(node, result = []) {
if (!node) return;
if (!Array.isArray(node.children)) return;
for (let entry of node.children) {
result.push(entry.id);
getChildIds(entry, result);
}
return result;
}
It might be a good idea to split your problem into two smaller problems:
Find a group of ID x somewhere nested in the graph
Given a node, return all their sub-node IDs recursively
The solution to the first problem could look something like this:
function findGroupId(o, id) {
if (o.id == id) {
// We found it!
return o;
}
if (Array.isArray(o)) {
// If we start with a list of objects, pretend it is the root node
o = {children: o}
}
let results = [];
for (let c of o.children) {
// recursively call this function again
results.push(findGroupId(c, id))
}
// return the first matching node
return results.filter(r => r !== undefined)[0];
}
And for the second problem:
function getAllChildrenIDs(o) {
if (o.children === undefined)
return [];
let ids = [];
for (c of o.children) {
ids.push(c.id);
// recursively call this function again
for (id of getAllChildrenIDs(c))
ids.push(id);
}
return ids;
}
And if we put this together:
let example = [{
id: 1,
name: 'Desjardins Group 1',
children: [{
id: 2,
name: 'Analysts',
children: []
},
{
id: 3,
name: 'Administration',
children: []
}
]
},
{
id: 4,
name: 'Desjardins Group 2',
children: [{
id: 5,
name: 'Consultants1',
children: []
},
{
id: 6,
name: 'Consultant2',
children: [{
id: 7,
name: 'Interns',
children: [{
id: 8,
name: 'subInterns1',
children: []
},
{
id: 9,
name: 'subInterns2',
children: []
},
{
id: 10,
name: 'subInterns3',
children: []
}
]
}]
}
]
}
];
function findGroupId(o, id) {
if (o.id == id) {
return o;
}
if (Array.isArray(o)) {
o = {
children: o
}
}
let results = [];
for (let c of o.children) {
results.push(findGroupId(c, id))
}
return results.filter(r => r !== undefined)[0];
}
function getAllChildrenIDs(o) {
if (o.children === undefined)
return [];
let ids = [];
for (c of o.children) {
ids.push(c.id);
for (id of getAllChildrenIDs(c))
ids.push(id);
}
return ids;
}
console.log(getAllChildrenIDs(findGroupId(example, 6)))

Construct a hierarchy object from javascript objects

I'm trying to figure out how to construct a hierachy object from some smaller objects that I have. Here's some example data:
{ id: 1, name: 'Jackson', parent: null },
{ id: 2, name: 'Jordan', parent: 1 },
{ id: 3, name: 'Jefferson', parent: 1 },
{ id: 4, name: 'Elvis', parent: 2 },
{ id: 5, name: 'Sally', parent: null },
{ id: 6, name: 'Eric', parent: 4 }
This would be constructed into a HIerarchy object that should look like so:
{
'1': {
name: 'Jackson',
children: {
'2': {
name: 'Jordan',
children: {
'4': {
name: 'Elvin',
children: {
'6': {
name: 'Eric',
children: { }
}
}
} },
'3': {
name: 'Jefferson',
children: { } }
}
},
'5': {
name: 'Sally',
children: { }
}
I'm really having a hard time figuring this out other then doing a for-loop for every id. (Ie: find all with null parent, find all with 1 parent, find all with 2 parent, etc...)
Here's my take on how to do this.
Create an object that will hold references to all the other objects, based on their key. This allows us to get a reference to each object by its key at the moment we need it as a parent of another element:
let register = {};
Create our output object:
let output = {};
Now let's go through each object in the array and add it to the structure:
// loop through the array
for (let el of elements) {
// clone the element, so we don't modify the original array
el = Object.assign({}, el);
// insert the clone into the register
register[el.id] = el;
if (!el.parent) { // if no parent is set, add it to the top level
output[el.id] = el;
} else { // otherwise, add it as a child of whatever the parent is
register[el.parent].children[el.id] = el;
}
// add a children property
el.children = {};
// remove the parent property
delete el.parent;
}
Remember that objects are always stored by reference, so modifying an object in the register object also modifies it in the output object.
Below is a working example.
let input = [{
id: 1,
name: 'Jackson',
parent: null
}, {
id: 2,
name: 'Jordan',
parent: 1
}, {
id: 3,
name: 'Jefferson',
parent: 1
}, {
id: 4,
name: 'Elvis',
parent: 2
}, {
id: 5,
name: 'Sally',
parent: null
}, {
id: 6,
name: 'Eric',
parent: 4
}];
let register = {};
let output = {};
// loop through the array
for (let el of input) {
// clone the element, so we don't modify the original array
el = Object.assign({}, el);
// insert the clone into the register
register[el.id] = el;
if (!el.parent) { // if no parent is set, add it to the top level
output[el.id] = el;
} else { // otherwise, add it as a child of whatever the parent is
register[el.parent].children[el.id] = el;
}
// add a children property
el.children = {};
// remove the parent property
delete el.parent;
}
console.log(JSON.stringify(output, undefined, 2));
Note that this will not work with circular references, or if the keys are out of order (i.e. the child appears before its parent).

Categories