My goal is to create a tree-like structure that automatically orders the following data.
The subSkills property contains a list of id references to other skills.
[
{
name: "chess";
subSkills: [];
parentId: "games";
_id: "chess";
},
{
name: "games";
subSkills: ["chess",...];
parentId: "";
_id: "games";
},
]
export default interface ISkill {
name: string;
subSkills: string[] | ISkill[];
parentId: string;
_id: string;
}
The result should be something like this.
[
{
name: "games";
subSkills: [
{
name: "chess";
subSkills: [{}...];
parentId: "games";
_id: "chess";
}
];
parentId: "";
_id: "games";
}, ... {}
]
I should note that the function has to be able to handle any level of depth. As I have no experience with
stuff like this, I would appreciate it if anyone could describe their way of thinking.
Thanks in advance.
EDIT: I have multiple trees/roots in the database. So multiple skills have a "" as parentId.
Based to my understanding, each object of the given array will be either a root or a child of another root object. I don't know if you mistakenly filled the subSkills array of an object or is it something to be considered so should be replaced with the whole object if found? per my assumption I just don't have subSkills as strings for now, I set all subSkills to empty array to start with, let me know if this something that should be considered please. otherwise you can actually just see if the string is met then you can remove it from the array and replace it with the child object itself.
Here's my solution:
const givenArray = [
{
name: "chess",
subSkills: [],
parentId: "games",
_id: "chess",
},
{
name: "games",
subSkills: [],
parentId: "",
_id: "games",
},
{
name: "programming dev",
subSkills: [],
parentId: "chess",
_id: "programming",
},
{
name: "basketball 01",
subSkills: [],
parentId: "chess",
_id: "basketball",
},
];
const skillsAggregator = (skills) => {
const newSkills = [...skills];
newSkills.forEach((skill) => {
if (!!skill.parentId.length) {
addSubSkills(newSkills, skill);
}
});
return newSkills;
};
const addSubSkills = (skills, currentSkill) => {
for (let i = 0; i < skills.length; i++) {
const skill = skills[i];
if (currentSkill.parentId === skill._id) {
skill.subSkills.push(currentSkill);
break;
}
}
};
console.log(JSON.stringify(skillsAggregator(givenArray), null, 2));
NOTE
If you can update your data structure (and you can) to a Map or a literal object, the algorithm will be faster than with array, here's an example with extra deep nesting level:
const givenArray = {
chess: {
name: "chess",
subSkills: [],
parentId: "games",
_id: "chess",
},
games: {
name: "games",
subSkills: [],
parentId: "",
_id: "games",
},
programming: {
name: "programming dev",
subSkills: [],
parentId: "chess",
_id: "programming",
},
basketball: {
name: "basketball 01",
subSkills: [],
parentId: "chess",
_id: "basketball",
},
football: {
name: "football",
subSkills: [],
parentId: "basketball",
_id: "football",
},
};
const skillsAggregator = (skills) => {
const newSkills = { ...skills };
Object.entries(newSkills).forEach(([id, skill]) => {
if (!!skill.parentId.length) {
newSkills[skill.parentId].subSkills.push(skill);
}
});
return newSkills;
};
console.log(JSON.stringify(skillsAggregator(givenArray), null, 2));
Related
I have a complex deeply nested JSON in Node.js and I have trouble extracting certain data. I need all the ids that belong only to the items, my problem is that the folders also have an id field so I cannot access only the id from the items. This is an example json:
{
id: "0",
name: null,
parentId: null,
folderType: "chatCannedMessages",
folders: [
{
id: 3195588631115178,
name: "Testfolder",
parentId: null,
folderType: "chatCannedMessages",
folders: [
{
id: "3195588620182363",
name: "Subfolder",
parentId: "3195588631115178",
folderType: "chatCannedMessages",
folders: [
{
id: "3206824598737435",
name: "Interesting",
parentId: "3195588620182363",
folderType: "chatCannedMessages",
folders: [],
items: [
{
id: "3208409930553392",
name: "Canned Message",
folderId: "3206824598737435",
updated: "2022-05-27T07:28:40.450Z",
frontendFolderId: null,
text: "<p>This is an HTML with Image.</p>",
keywords: "test",
subject: "What kind of subject",
slashCommand: "test",
language: "en-US",
setupItemId: "3208409930553392",
},
],
},
],
items: [
{
id: "3195595211854821",
name: "Message in subfolder",
folderId: "3195588620182363",
updated: "2022-05-19T12:05:39.503Z",
frontendFolderId: null,
text: "Message in subfolder",
keywords: "test",
subject: "Message in subfolder",
slashCommand: "sub",
language: "bn-BD",
setupItemId: "3195595211854821",
},
],
},
],
items: [],
},
],
items: [
{
id: "2888102250465731",
name: "bye",
folderId: null,
updated: "2022-05-25T11:15:36.367Z",
frontendFolderId: null,
text: "Thanks for contacting us. Please do not hesitate to contact us again if we can be of further assistance.",
keywords: "bye",
subject: null,
slashCommand: null,
language: null,
setupItemId: "2888102250465731",
},
],
};
The output that I want to achieve here is an array, looking like this:
["3208409930553392", "3195595211854821", null]
Iterate it is not that complex json. Just items and folders.
var obj={id:"0",name:null,parentId:null,folderType:"chatCannedMessages",folders:[{id:0xb5a5ef534b5aa,name:"Testfolder",parentId:null,folderType:"chatCannedMessages",folders:[{id:"3195588620182363",name:"Subfolder",parentId:"3195588631115178",folderType:"chatCannedMessages",folders:[{id:"3206824598737435",name:"Interesting",parentId:"3195588620182363",folderType:"chatCannedMessages",folders:[],items:[{id:"3208409930553392",name:"Canned Message",folderId:"3206824598737435",updated:"2022-05-27T07:28:40.450Z",frontendFolderId:null,text:"<p>This is an HTML with Image.</p>",keywords:"test",subject:"What kind of subject",slashCommand:"test",language:"en-US",setupItemId:"3208409930553392"},]},],items:[{id:"3195595211854821",name:"Message in subfolder",folderId:"3195588620182363",updated:"2022-05-19T12:05:39.503Z",frontendFolderId:null,text:"Message in subfolder",keywords:"test",subject:"Message in subfolder",slashCommand:"sub",language:"bn-BD",setupItemId:"3195595211854821"},]},],items:[]},],items:[{id:"2888102250465731",name:"bye",folderId:null,updated:"2022-05-25T11:15:36.367Z",frontendFolderId:null,text:"Thanks for contacting us. Please do not hesitate to contact us again if we can be of further assistance.",keywords:"bye",subject:null,slashCommand:null,language:null,setupItemId:"2888102250465731"},]};
result = [];
function iterate(obj) {
(obj.items || []).forEach(function(item) {
result.push(item.id)
});
(obj.folders || []).forEach(function(item) {
iterate(item)
});
}
iterate(obj);
console.log(result)
const data = {
id: "0",
name: null,
parentId: null,
folderType: "chatCannedMessages",
folders: [{
id: 3195588631115178,
name: "Testfolder",
parentId: null,
folderType: "chatCannedMessages",
folders: [{
id: "3195588620182363",
name: "Subfolder",
parentId: "3195588631115178",
folderType: "chatCannedMessages",
folders: [{
id: "3206824598737435",
name: "Interesting",
parentId: "3195588620182363",
folderType: "chatCannedMessages",
folders: [],
items: [{
id: "3208409930553392",
name: "Canned Message",
folderId: "3206824598737435",
updated: "2022-05-27T07:28:40.450Z",
frontendFolderId: null,
text: "<p>This is an HTML with Image.</p>",
keywords: "test",
subject: "What kind of subject",
slashCommand: "test",
language: "en-US",
setupItemId: "3208409930553392",
}, ],
}, ],
items: [{
id: "3195595211854821",
name: "Message in subfolder",
folderId: "3195588620182363",
updated: "2022-05-19T12:05:39.503Z",
frontendFolderId: null,
text: "Message in subfolder",
keywords: "test",
subject: "Message in subfolder",
slashCommand: "sub",
language: "bn-BD",
setupItemId: "3195595211854821",
}, ],
}, ],
items: [],
}, ],
items: [{
id: "2888102250465731",
name: "bye",
folderId: null,
updated: "2022-05-25T11:15:36.367Z",
frontendFolderId: null,
text: "Thanks for contacting us. Please do not hesitate to contact us again if we can be of further assistance.",
keywords: "bye",
subject: null,
slashCommand: null,
language: null,
setupItemId: "2888102250465731",
}, ],
};
const ids = []
function traverse(obj, parent) {
if (obj === null) return
if (Array.isArray(obj)) {
for (const item of obj) {
traverse(item, parent)
}
} else if (typeof obj === 'object') {
if (parent === 'items') {
ids.push(obj.id)
} else {
for (const [key, value] of Object.entries(obj)) {
traverse(value, key)
}
}
}
}
traverse(data, '')
console.log(ids)
You can solve your problem by applying a recursive logic. The pattern is that if the last descendant object was items, then any direct object descendants (with possible arrays in-between of the hierarchy) are having ids that we need to collect
let input = {
items: [
{
id: 1,
foo: [
{
id: 2,
items: [
{
id: 3,
items: [
{
bar: [
{
id: 4
}
]
},
{
id: 5
}
]
}
]
}
]
}
]
}
let ids = [];
function getItemIDs(obj, wasItems = false) {
if (Array.isArray(obj) || (typeof obj === "object")) {
if (wasItems && (!Array.isArray(obj))) {
ids.push(obj.id);
}
for (let key in obj) {
if (Array.isArray(obj[key]) || (typeof obj[key] === "object")) {
getItemIDs(obj[key], (key === "items") || (wasItems && !Array.isArray(obj[key])))
}
}
}
}
getItemIDs(input);
console.log(ids);
This question already has answers here:
Build tree array from flat array in javascript
(34 answers)
Closed 7 months ago.
i am trying to create a multidimensional array from a flat array. but i somehow i can not figure it out.
I manage it to get it 1lvl deep but can not figure out how to go all the way:
example :
[
{ id: 'cG9zdDoyODI=', parentId: null, children: [] },
{ id: 'cG9zdDoyODM=', parentId: null, children: [] },
{ id: 'cG9zdDoyODc=', parentId: null, children: [] },
{ id: 'cG9zdDoyODY=', parentId: 'cG9zdDoyODc=', children: [] },
{ id: 'cG9zdDozMDE=', parentId: 'cG9zdDoyODY=', children: [] },
{ id: 'cG9zdDozMDI=', parentId: 'cG9zdDozMDE=', children: [] },
{ id: 'cG9zdDoyODQ=', parentId: 'cG9zdDoyODc=', children: [] },
{ id: 'cG9zdDoyODU=', parentId: 'cG9zdDoyODc=', children: [] }
]
i am trying to place each object that has a parent into the parents children array.
all the help is welcome, thanks in advance
If I understood that good, you wnat to do this:
const data = [
{ id: 'cG9zdDoyODI=', parentId: null, children: [] },
{ id: 'cG9zdDoyODM=', parentId: null, children: [] },
{ id: 'cG9zdDoyODc=', parentId: null, children: [] },
{ id: 'cG9zdDoyODY=', parentId: 'cG9zdDoyODc=', children: [] },
{ id: 'cG9zdDozMDE=', parentId: 'cG9zdDoyODY=', children: [] },
{ id: 'cG9zdDozMDI=', parentId: 'cG9zdDozMDE=', children: [] },
{ id: 'cG9zdDoyODQ=', parentId: 'cG9zdDoyODc=', children: [] },
{ id: 'cG9zdDoyODU=', parentId: 'cG9zdDoyODc=', children: [] }
];
data.forEach(parent=>data.find(elem=>elem.parentId===parent.id && parent.children.push(elem)));
console.log(data);
let arr = [
{ id: 'cG9zdDoyODI=', parentId: null, children: [] },
{ id: 'cG9zdDoyODM=', parentId: null, children: [] },
{ id: 'cG9zdDoyODc=', parentId: null, children: [] },
{ id: 'cG9zdDoyODY=', parentId: 'cG9zdDoyODc=', children: [] },
{ id: 'cG9zdDozMDE=', parentId: 'cG9zdDoyODY=', children: [] },
{ id: 'cG9zdDozMDI=', parentId: 'cG9zdDozMDE=', children: [] },
{ id: 'cG9zdDoyODQ=', parentId: 'cG9zdDoyODc=', children: [] },
{ id: 'cG9zdDoyODU=', parentId: 'cG9zdDoyODc=', children: [] }
];
let parents = arr.filter(elem => elem.parentId === null); // filtered out parents
parents.map(elem => {
arr.forEach(child => {
if (elem.id === child.parentId) { // matching children by id
elem.children.push(child); // pushing children into parents
}
});
});
I have this nested object :
export interface RenderTree {
id: string;
name: string;
children?: readonly RenderTree[];
}
export const UserLoginRoleV2Tree: RenderTree = {
id: "root",
name: "APP",
children: [
{
id: "1",
name: UserLoginRoleV2.DASHBOARD
},
{
id: "2",
name: UserLoginRoleV2.REPORTING,
children: [
{
id: "2.1",
name: UserLoginRoleV2.REPORTS
},
{
id: "2.2",
name: UserLoginRoleV2.SALES_DETAILS
},
{
id: "2.3",
name: UserLoginRoleV2.LEADS_DETAILS
}
]
},
{
id: "3",
name: UserLoginRoleV2.SALES_DATA,
children: [
{
id: "3.1",
name: UserLoginRoleV2.SALES
},
{
id: "3.2",
name: UserLoginRoleV2.LEADS
},
{
id: "3.3",
name: UserLoginRoleV2.CALLS
},
{
id: "3.4",
name: UserLoginRoleV2.PHONE_CLOSING
}
]
},
{
id: "4",
name: UserLoginRoleV2.TRACKING,
children: [
{
id: "4.1",
name: UserLoginRoleV2.UNIVERSAL_SCRIPT
},
{
id: "4.2",
name: UserLoginRoleV2.SOURCE_LINKS
},
{
id: "4.3",
name: UserLoginRoleV2.URL_RULES
},
{
id: "4.4",
name: UserLoginRoleV2.PRODUCTS
},
{
id: "4.4",
name: UserLoginRoleV2.TAGS
}
]
},
{
id: "5",
name: UserLoginRoleV2.SETTINGS,
children: [
{
id: "5.1",
name: UserLoginRoleV2.PROFILE
},
{
id: "5.2",
name: UserLoginRoleV2.BILLING
},
{
id: "5.3",
name: UserLoginRoleV2.SUB_USERS
},
{
id: "5.4",
name: UserLoginRoleV2.CONNECTED_ACCOUNTS
},
{
id: "5.5",
name: UserLoginRoleV2.INTEGRATIONS
},
{
id: "5.6",
name: UserLoginRoleV2.TRACKING_SETTINGS
},
{
id: "5.7",
name: UserLoginRoleV2.TRACKING_DOMAINS
}
]
}
]
}
From this object, I'm able to build a Tree. Each node of the tree has a checkbox. To know if the checkbox is checked or not, I have a state array when I keep all nodes that are checked :
interface State {
roles: Array<string>;
}
<TreeItem key={nodes.id} nodeId={nodes.id} label={<Checkbox checked={this.state.roles.includes(nodes.name)} onClick={...}>
{Array.isArray(nodes.children)
? nodes.children.map((node) => this.renderTree(node))
: null}
</TreeItem>
I'm looking for a way to remove all children of a node from the array when clicking on it, knowing that a child can have children too.
I feel that we could make it with a recursive function, but I don't have success with that for now. Can you help me?
Okay I resolved it with Depth-first search
/**
* Get all children of a node.
* #param branch The node whose children you want to know
* #param callback Function to apply on each node.
*/
const breadthFirstSearch = (branch: TreeBranch): TreeBranch[] => {
let queue = new Array(branch);
let explored = new Array(branch);
let res = new Array();
let b: TreeBranch;
while (queue.length !== 0) {
b = queue.shift();
res.push(b);
b.children?.forEach(child => {
if (!explored.includes(child)) {
queue.push(child);
explored.push(child);
}
})
}
return res;
}
I'm migrating from python to javascript. So now, I'm working on a react project where I need to convert some unix-like path to json. Actually, there aren't folders, they're list of categories joined by "/".
Here is what I have:
Category Model holds the categories and the slug play as categories list joined by "/"
const categorySchema = mongoose.Schema({
name: { type: String, required: true },
slug: { type: String, unique: true }, // Repr the route
image: { type: String },
topLevel: { type: String },
description: { type: String },
}, { timestamps: true });
One representation of a category is like this:
{
name: "embedded",
slug: "/electronics/embedded",
image: "/some/image.png",
topLevel: "electronics",
description: "This is a brief description."
}
Now what I want is when I have a list of object like this
[
{
id: 1,
name: "embedded",
slug: "/electronics/embedded",
image: "/some/image.png",
topLevel: "electronics",
description: "This is a brief description."
},
{
id: 2,
name:"electonics",
slug:"/electronics",
topLevel: "electronics",
image: "/some/image.png",
description: "..."
},
{
id: 3,
name: "house",
slug: "/house",
topLevel: "house",
image: "/some/image.png",
description: "...",
}
]
to end up having from the slug which are the unix-like paths as:
[
{
id: 2,
node: "electronics",
children: [{
id: 1,
node: "embedded",
children: [],
}],
},
{
id: 3,
node: "house",
children: []
},
]
This is what I'm really struggling trying to have. If someone can help, you are more than welcome.
Based on yesterday's comment on whether I've tried something yet or not, I have basically come up with something but still not perfect because I didn't walk deep the whole tree.
Based on the input data and the type of output I want to have, this is my first trial:
function listToTree(list) {
var node,
i,
roots = [];
for (i = 0; i < list.length; i += 1) {
node = list[i];
var slugList = node.slug.split("/").filter((x) => x !== "");
var children = [];
if (slugList.length > 1) {
var parent = slugList.shift();
var parentNode = {
node: parent,
id: node.id,
children: [],
};
roots.push(parentNode);
for (var j = 0; j < slugList.length; j++) {
var child = {
id: node.id,
node: slugList[j],
children: [],
};
children.push(child);
}
roots[i].children = [...children];
} else {
roots.push({
node: node.topLevel,
id: node.id,
children: [],
});
}
}
return roots;
}
The output:
const roots = listToTree(exampleData);
console.log(roots[0]);
{
node: 'electronics',
id: 1,
children: [ { id: 1, node: 'embedded', children: [] } ]
}
So, I still need some help and to be honest this answer here, not yet perfect, was inspired by the answered made by Halcyon in here
I am getting the users navigation "state" in a mobile navigation as an Array. For example:
['3124', '5312', '5232']
I need to use that state, to grab the Object that has the ID of '5232', 3 levels down in the object.
The Array length can differ, meaning it can return between 1 and 5 ids, so I don't always have to loop all the way down.
This is what the data for the navigation can look like, using the same IDs as I used in the example above, I would like my function to return the "evening" object with ID '5232':
[
{
id: "3124",
name: "women",
children: [
{
id: "5312",
name: "dresses",
children: [
{
id: "8399",
name: "wedding",
children: []
},
{
id: "5232",
name: "evening",
children: []
}
]
},
{
id: "3291",
name: "shoes",
children: []
}
]
},
{
id: "9482",
name: "men",
children: [
{
id: "8292",
name: "jackets",
children: []
},
{
id: "3829",
name: "hats",
children: []
}
]
}
]
I've been talking this through with a couple of colleagues and we can't really figure out a good way to do this efficiently. We cannot change the data, but we can probably change how the user state is saved, if that is wrong.
I could really use some input and ideas on how to solve this problem in a good way.
Simple function to find node by path in tree
function findNodeByPath(nodes, path) {
let node;
if (!path) return;
for (let id of path) {
if (!nodes) break;
for (let child of nodes) {
if (child.id === id) {
node = child;
nodes = node.children;
break;
}
}
}
return node;
}
let nodes = [
{
id: "3124",
name: "women",
children: [
{
id: "5312",
name: "dresses",
children: [
{
id: "8399",
name: "wedding",
children: []
},
{
id: "5232",
name: "evening",
children: []
}
]
},
{
id: "3291",
name: "shoes",
children: []
}
]
},
{
id: "9482",
name: "men",
children: [
{
id: "8292",
name: "jackets",
children: []
},
{
id: "3829",
name: "hats",
children: []
}
]
}
];
console.log(findNodeByPath(nodes, ['3124', '5312', '5232']));
You could iterate the arrays and take only the node of the given path id.
function getObject(tree, path) {
var temp = { children: tree };
return path.every(p => temp = temp.children.find(({ id }) => p === id))
? temp
: undefined;
}
var data = [{ id: "3124", name: "women", children: [{ id: "5312", name: "dresses", children: [{ id: "8399", name: "wedding", children: [] }, { id: "5232", name: "evening", children: [] }] }, { id: "3291", name: "shoes", children: [] }] }, { id: "9482", name: "men", children: [{ id: "8292", name: "jackets", children: [] }, { id: "3829", name: "hats", children: [] }] }],
path = ['3124', '5312', '5232'],
result = getObject(data, path);
console.log(result);
You can try implementing a basic nested for loop if focusing solely on efficiency. I chose the variable name breadcrumb since the concept seems similar to its role in site navigation.
function getState(breadcrumb, state) {
let states = state;
for (let id of breadcrumb) {
for (state of states) {
// found -- continue to next level
if (state.id === id) {
states = state.children;
break;
}
}
// not found
if (state.id !== id) {
return null;
}
}
return state;
}
let state = [{ id: "3124", name: "women", children: [{ id: "5312", name: "dresses", children: [{ id: "8399", name: "wedding", children: [] }, { id: "5232", name: "evening", children: [] }] }, { id: "3291", name: "shoes", children: [] }] }, { id: "9482", name: "men", children: [{ id: "8292", name: "jackets", children: [] }, { id: "3829", name: "hats", children: [] }] }];
let breadcrumb = ['3124', '5312', '5232'];
console.log(getState(breadcrumb, state));
However, if you care more about code maintainability, I recommend a more canonical approach:
function getState(breadcrumb, state) {
return breadcrumb.reduce((state, id) => {
return state !== null
? state.children.find(state => state.id === id)
: state;
}, { children: state });
}
let state = [{ id: "3124", name: "women", children: [{ id: "5312", name: "dresses", children: [{ id: "8399", name: "wedding", children: [] }, { id: "5232", name: "evening", children: [] }] }, { id: "3291", name: "shoes", children: [] }] }, { id: "9482", name: "men", children: [{ id: "8292", name: "jackets", children: [] }, { id: "3829", name: "hats", children: [] }] }];
let breadcrumb = ['3124', '5312', '5232'];
console.log(getState(breadcrumb, state));
try using recursive function
function findById(id) {
var founded = {};
function recurse(data){
for(var i = 0; i < data.length; i++) {
if (data[i].id === id) {
founded = data[i];
} else if (data[i].children && data[i].children.length) {
recurse(data[i].children);
}
}
}
recurse(catalog);
return founded;
};
some demo : Demo