Creating a hierarchical tree structure from an array in JavaScript

I have a list that looks like this:

[
  "parent1|child1|subChild1",
  "parent1|child1|subChild2",
  "parent|child2|subChild1",
  "parent1|child2|subChild2",
  "parent2|child1|subChild1",
  "parent2|child1|subChild2",
  "parent2|child2|subChild1",
.
.
.    
]

The first string before | represents the parent, the second string before | represents the child, and the third string after the second | represents the subchild.

How can I transform this list into an object like this:

[
 {
  "id": "parent1",
  "children":[
   {
    "id": "child1",
    "children":[
     {
      "id": "subChild1"
     }
    ]
   }
  ]
 }
]

This structure follows the pattern of Parent -> Child -> Subchild.

Following Sebastian's advice, I attempted the following using TypeScript:

private genTree(row) {
        let self = this;
        if (!row) {
            return;
        }
        const [parent, ...children] = row.split('|');
        if (!children || children.length === 0) {
            return [{
                id: parent,
                children: []
            }];
        }
        return [{
            id: parent,
            children: self.genTree(children.join('|'))
        }];
    }

    private mergeDeep(children) {
        let self = this;
        const res = children.reduce((result, curr) => {
            const entry = curr;
            const existing = result.find((e) => e.id === entry.id);
            if (existing) {
                existing.children = [].concat(existing.children, entry.children);
            } else {
                result.push(entry);
            }
            return result;
        }, []);
        for (let i = 0; i < res.length; i++) {
            const entry = res[i];
            if (entry.children && entry.children.length > 0) {
                entry.children = self.mergeDeep(entry.children);
            }
        };
        return res;
    }

private constructTree(statKeyNames){
    let self = this;
    const res = this.mergeDeep(statKeyNames.map(self.genTree.bind(this)).map(([e]) => e));
    console.log(res);
}

Unfortunately, this led to the error:

"Cannot read property 'genTree' of undefined"

Update:

After making the change from self.genTree to this.genTree.bind(this) as per Sebastian's suggestion, the issue was resolved successfully without any further problems.

Answer №1

To achieve this, you can create a 'mapper' object that associates each object with its unique path (not necessarily based on the object's id). This method involves using the 'reduce' function on each partial item in an array. The initial value for this process would be the 'root' object, while the accumulator would represent the parent object of the current item being processed. Eventually, you return the current object during each iteration.

const input = [
    "parent1|child1|subChild1",
    "parent1|child1|subChild2",
    "parent1|child2|subChild1",
    "parent1|child2|subChild2",
    "parent2|child1|subChild1",
    "parent2|child1|subChild2",
    "parent2|child2|subChild1"
  ],
  mapper = {},
  root = { children: [] }

for (const str of input) {
  let splits = str.split('|'),
      path = '';

  splits.reduce((parent, id, i) => {
    path += `${id}|`;

    if (!mapper[path]) {
      const o = { id };
      mapper[path] = o; // creating a new object with a unique path
      parent.children = parent.children || [];
      parent.children.push(o)
    }
    
    return mapper[path];
  }, root)
}

console.log(root.children)

Answer №2

If you want to achieve this, recursion is the way to go. Check out the example below:

const arr = [
  "parent1|child1|subChild1",
  "parent1|child1|subChild2",
  "parent|child2|subChild1",
  "parent1|child2|subChild2",
  "parent2|child1|subChild1",
  "parent2|child1|subChild2",
  "parent2|child2|subChild1"
];

function genTree(row) {

  const [parent, ...children] = row.split('|');

  if (!children || children.length === 0) {
    return [{
      id: parent,
      children: []
    }];
  }

  return [{
    id: parent,
    children: genTree(children.join('|'))
  }];
};

function mergeDeep(children) {

  const res = children.reduce((result, curr) => {

    const entry = curr;

    const existing = result.find((e) => e.id === entry.id);
    if (existing) {

      existing.children = [].concat(existing.children, entry.children);
    } else {
      result.push(entry);
    }

    return result;
  }, []);

  for (let i = 0; i < res.length; i++) {

    const entry = res[i];
    if (entry.children && entry.children.length > 0) {
      entry.children = mergeDeep(entry.children);
    }
  };

  return res;
}

const res = mergeDeep(arr.map(genTree).map(([e]) => e));
console.log(JSON.stringify(res, false, 2));

In this solution, I've utilized two helper functions: genTree(row) which recursively constructs a simple tree from each row, and mergeDeep(children) which amalgamates the first-level trees in the outcome of arr.map(genTree).map(([e]) => e). It then goes through the array and recursively applies the same process to all children of each entry.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Attempting to grasp the concept of implementing Promise in JavaScript

Currently, I am utilizing the native driver for mongoDB in my project. Within the database, there are approximately 7 collections that I need to work with. My goal is to create a variable that holds the count of entries in each collection except for the la ...

Incorporating TypeScript's internal references

I am currently working on defining my own model interface that extends the Sequelize model instance. However, I am encountering difficulties in referencing the Sequelize interface within my code. Specifically, I receive an error stating "Cannot find name ...

Angular 2 wrap-up: How to seamlessly transfer filter data from Filter Component to App Component

A filtering app has been created successfully, but there is a desire to separate the filtering functionality into its own component (filtering.component.ts) and pass the selected values back to the listing component (app.ts) using @Input and @Output functi ...

What is the most concise way to retrieve the ids of all elements belonging to a specified tag in JavaScript?

I need to extract all unique IDs from foobar elements on a page and create a comma-separated list. Here's an example: <div> <foobar id="456"></foobar> <foobar id="aaa"></foobar> <foobar id="987"></foobar&g ...

Numerous AJAX requests are queued up, waiting for each other to execute

Why do my ajax requests wait for the previous one to complete before starting? Check out this snippet of JS code I'm using: tjq.ajax({ type: 'POST', data: { id: _id }, dataType: 'json', url: ' ...

Unspecified variables in a Javascript bot

Currently, I am working on a project involving the Kik API to create a bot. The main goal is for the game to initiate when users type "!hangman". A boolean value called hangman activates this process and then becomes inactive. Players can then input "!ha ...

Error occurred in the middle of processing, preventing the headers from being set

I created a custom authentication middleware, but encountered an error. I'm puzzled about what's going wrong because I expected the next() function to resolve the issue? app.use(function(req, res, next){ if(req.user){ res.local ...

Vue.js event change method does not initiate the trigger

I have been attempting to implement a change event in my Vue application, where a statement switches between true and false each time I check a checkbox. However, the functionality doesn't seem to be working as expected. This issue arose while follow ...

Modify state of parent component in React Functional Components without re-rendering all child components

Utilizing hooks in React to change the parent component's state from a child component can be achieved by sharing a callback from parent to child, as detailed in various resources such as this guide and this article: function Parent() { const [val ...

Persist a SQL entity to a document with the help of Node.js

I am looking for a way to store the data from the rows object either in a file or as a JSON file. app.get('/getposts', (req, res) => { mysqlConnection.query('Select * from posts', (err, rows, fields) => { if (!err) console.l ...

Encountering difficulties with implementing and utilizing bootstrap in Symfony 5.3 with Encore plugin

While I am currently dealing with an older version of Symfony, I decided to create a new 5.3 Symfony application from scratch. However, I am facing difficulties when trying to integrate bootstrap into it. After consulting some documentation, I proceeded to ...

Issue with JQuery UI buttonset functionality when using radio buttons with the runat="server" attribute assigned

I'm currently working on a demo application where I need to use radio buttons as buttons. To achieve this, I'm utilizing the JQuery UI buttonset widget. Everything was functioning correctly until I decided to add the attribute runat="server" in o ...

Transform object into an array by flattening it

I'm currently working on a task where I have an object that needs to be transformed into an array with the property as the key. The structure of the object is as follows: { Cat: { value: 50 }, Dog: { value: 80 } } The desired output should ...

Tips for effectively modeling data with AngularJS and Firebase: Deciding when to utilize a controller

While creating a project to learn AngularJS and Firebase, I decided to build a replica of ESPN's Streak for the Cash. My motivation behind this was to experience real-time data handling and expand my knowledge. I felt that starting with this project w ...

Google Sheets - Create a randomized list of numerical values (specified quantity) within a certain range (set numbers) that collectively equal a specific total

Here is an interesting challenge... I am trying to find a way to generate a specific number of random values within a range that add up to a predetermined total in Google Sheets. For example, I want to create a list of 10 numbers that sum up to 100, with ...

Place a div according to the location of the mouse click

Is there a way to create a popup in Angular 4 that appears next to the mouse click coordinates? I want it to work similar to how events are created in Google Calendar. ...

Ensure Typescript Filters Out Unnecessary Data in POST Requests

When making a post request, the required fields are content_name, content_type, and content_json. However, adding an extra property could result in sending unwanted data. How can this be prevented? Data Transfer Object (DTO): content_name, content_type, C ...

Triggering an event between 2 AngularJS controllers using bidirectional binding

This is a question specifically regarding Angular 1, not Angular 2. I have a unique setup where I have Controller A responsible for a specific page. On this page, there is a custom directive that takes input from Controller A but also has its own controll ...

Maintain the tab order for elements even when they are hidden

Check out this demonstration: http://jsfiddle.net/gLq2b/ <input value="0" /> <input id="test" value="1" /> <input value="2" /> By pressing the TAB key, it will cycle through the inputs in order. If an input is hidden when focused, press ...

Issues with looping in Internet Explorer 8

Hey, I'm having an issue with this JavaScript function: function test(){ var count = 0; var date1 = $('#alternatestartdate').val(); var date2 = $('#alternateenddate').val(); ...