Organize an array of objects in JavaScript into a structure with nested children

I am facing a challenge with organizing an array of objects based on parentId and sort values. I need to create a nested array with 'children' and ensure proper sorting.

Consider the following data:

[{
    id: 1,
    sort: 2,
    parentId: null,
    name: 'A'
}, {
    id: 2,
    sort: 1,
    parentId: 1,
    name: 'A.1'
}, {
    id: 3
    sort: 2,
    parentId: 1,
    name: 'A.2'
}, {
    id: 4,
    sort: 1,
    parentId: null,
    name: 'B'
}]

The desired transformation should look like this:

[{
    id: 4,
    sort: 1,
    parentId: null,
    name: 'B',
    children: []
}, {
    id: 1,
    sort: 2,
    parentId: null,
    name: 'A',
    children: [{
        id: 2,
        sort: 1,
        parentId: 1,
        name: 'A.1'
    }, {
        id: 3
        sort: 2,
        parentId: 1,
        name: 'A.2'
    }]
}]

The sorting is based on the 'sort' value, with id 4 being at the top. The 'children' are nested and sorted accordingly.

I am seeking suggestions on an efficient approach to achieve this. I can use recursive loops to apply children, but I need assistance in maintaining the sorting order.

Answer №1

This proposal suggests a two-step process of sorting followed by filtering.

In the sorting step, the properties parentId and sort are used to order the array. This step is crucial for the subsequent filtering operation, which requires a pre-sorted array.

For filtering, the array is processed using the Array#filter() method, with thisArgs employed to reference nodes for potential child insertion.

Edit: An update is provided for handling unsorted data with id/parentId.

var array = [{ id: 1, sort: 2, parentId: null, name: 'A' }, { id: 2, sort: 1, parentId: 1, name: 'A.1' }, { id: 3, sort: 2, parentId: 1, name: 'A.2' }, { id: 4, sort: 1, parentId: null, name: 'B' }],
    nested;

array.sort(function (a, b) {
    return (a.parentId || -1) - (b.parentId || -1) || a.sort - b.sort;
});

nested = array.filter(function (a) {
    a.children = this[a.id] && this[a.id].children;
    this[a.id] = a;
    if (a.parentId === null) {
        return true;
    }
    this[a.parentId] = this[a.parentId] || {};
    this[a.parentId].children = this[a.parentId].children || [];
    this[a.parentId].children.push(a);
}, Object.create(null));

document.write('<pre>' + JSON.stringify(nested, 0, 4) + '</pre>');

Answer №2

I decided to give it a shot and after some reflection, I've returned to find that there are already multiple responses. However, I'm still going to share my thoughts on the matter.

This approach involves making changes directly to the original Array:

var items = [{id: 1,sort: 2,parentId: null,name: 'A'}, {id: 2,sort: 1,parentId: 1,name: 'A.1'}, {id: 3,sort: 2,parentId: 1,name: 'A.2'}, {id: 4,sort: 1,parentId: null,name: 'B'}];


function generate_tree(arr){
  var references = {};
  arr.sort(function(a,b){
    // While sorting, keep references to each item by id
    references[a.id] = a; references[b.id] = b;
    // Introduce a children property
    a.children = []; b.children = [];
    if(a.sort > b.sort) return 1;
    if(a.sort < b.sort) return -1;
    return 0;
  });

  for(var i=0; i<arr.length; i++){
    var item = arr[i];
    if(item.parentId !== null && references.hasOwnProperty(item.parentId)){
      references[item.parentId].children.push(arr.splice(i,1)[0]);
      i--; // Adjusting index since it now holds the next item
    }
  }
  return arr;
}

document.body.innerHTML = "<pre>" + JSON.stringify(generate_tree(items), null, 4) + "</pre>";

Answer №3

  1. To redesign the data structure, consider the following format:

    { 1: { id: 1, sort: 2, parentId: null, name: 'A' }, 2: { id: 4, sort: 1, parentId: null, name: 'B' } }

Key points to note: the updated structure is in object form, not an array, and only includes the top-level elements (those with a parentId of null)

  1. Next, iterate over the original array using a for loop and assign

    new_obj[ orig_arr_curr_elem[parentId] ].children.push(orig_arr_curr_elem)

  2. Create a new array with elements from new_obj and sort() the array (or the children property) based on your preference

Below is the code implementing steps 1 and 2 (execute this in node):

var util = require('util');
var old_arr = [{
    id: 1,
    sort: 2,
    parentId: null,
    name: 'A'
}, {
    id: 2,
    sort: 1,
    parentId: 1,
    name: 'A.1'
}, {
    id: 3,
    sort: 2,
    parentId: 1,
    name: 'A.2'
}, {
    id: 4,
    sort: 1,
    parentId: null,
    name: 'B'
}];

var new_obj = {};
for (var i = 0; i < old_arr.length; i++){
    if ( old_arr[i].parentId == null )
        new_obj[ old_arr[i].id ] = old_arr[i];
}

for (var i = 0; i < old_arr.length; i++){
    if ( old_arr[i].parentId == null ) continue;
    new_obj[ old_arr[i].parentId ].children = new_obj[ old_arr[i].parentId ].children || [];
    new_obj[ old_arr[i].parentId ].children.push( old_arr[i] );
}

console.log(util.inspect(new_obj, {showHidden: false, depth: null}));

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

The nightwatch.js script is halting operations once the test suite has been completed

Recently, I've implemented functional test automation using nightwatch.js. However, I encountered an issue where the test pauses after the test suite is completed, failing to end the process. Below is a snippet of the code: var afterSuite = function( ...

Displaying URLs stylishly with Pills Bootstrap

When a pill is selected from the list, I need to display a specific URL that can be used elsewhere. However, there is an href="#pills-something" attribute that directs to an ID below. I am looking for something like: mysite.com/myspecificpill or ...

Implement a customized toString method for components in ReactJS

Looking to modify the toString method of a class component in reactjs? Check out the code snippet below class C1 extends React.Component{ render(){ return ( <div> {C2.toString()} </div> ) } } class C2 extend ...

"Utilizing datatables to efficiently submit form data to the server-side

I'm curious for those who utilize the Datatables javascript plugin, is there a way to replicate this specific example using server-side data? The current example I came across has its data hardcoded directly in the HTML. ...

How to translate a list of integers into JSON format using Java

Can someone help me with converting an array of integers into JSON format? The array is created in Java and I need it in the form of a JSONArray or JSONObject. Here's my code: int[] tableau = new int[6]; JSONArray jsonArray = new JSONArray(); int k ...

The Bootstrap tooltip effectively fades away after displaying text, but it is not positioned correctly above the icon

Having some issues with my tooltip functionality. It seems to display the text on the left side and fades away upon mouseover, but it doesn't show up in a proper tooltip box over the icon as expected. I suspect that there might be a conflict between j ...

Transforming multi-dimensional arrays into database structures

I am faced with the challenge of converting a complex configuration array in PHP into a database-friendly format. The array structure is extensive and contains details such as festival information, registration layout, page layout, event options, and more. ...

Unexpected behavior from Bootstrap within React

I recently started working on a React project that I initiated with the create-react-app command. To incorporate Bootstrap into my project, I added the necessary CDNs to the public/index.html file after generating the project. <link rel="stylesheet" hr ...

At times, MomentJS may miscalculate and add an incorrect number of hours

My goal is to add a specific amount of hours to a 24-hour time, but I'm encountering an issue with the time 00:00. The code I've written works correctly for all times except midnight. For example, if I have 01:30 and add 1 hour, it gives me 02:3 ...

Guidelines for accessing the value of the parent function upon clicking the button within the child function?

I have a pair of buttons labeled as ok and cancel. <div class="buttons-div"> <button class='cancel'>Cancel</button> <button class='ok'>Ok</button> </div> The functions I am working wi ...

Utilizing JavaScript for form creation

Still learning the ropes of JavaScript and feeling a bit unsure about my skills. I'm trying to use JavaScript to create a new window with an input form within it. Managed to get a basic window set up with a dropdown menu, but struggling to implement ...

Press the Enter key to submit

Encountering issues while trying to enter an event. Despite reviewing several posts on this matter, a solution has not been found yet. The project needs to function properly in Chrome, FF & IE (8,9,10,11), but it is currently not working on any browser. T ...

implement a JavaScript function for sprite toggling

I have been working on a JS/jQuery function to switch the position of an icon sprite. I have successfully created the following code: $('.toggle-completed').mouseup(function(){ var sp = ($(this).css('background-position')); $(this).css ...

Retrieving the length of an array from Firebase Firestore

Recently, I dove into a project that involves using Next JS and Firebase. After successfully storing data in Cloud Firestore, I encountered an issue when trying to retrieve the length of an array from the database. Below is an image illustrating the data s ...

The issue with Google Maps API not loading is being caused by a problem with the function window.handleApiReady not being

Having trouble with the Google Maps API, specifically encountering an error during page load that says window.handleApiReady is not a function, even though it definitely exists. Examining the code snippet below reveals its usage as a callback function: ...

What's the process for assigning a webpack ChunkName in a Vue project?

I have encountered an issue with webpack Chunk. I have already installed "@babel/plugin-syntax-dynamic-import". Below is my configuration and import() code. Despite this, only the entry index.js file is being generated in the output folder. What could I be ...

Is there a way to format wrapped lines with indentation in an unordered list?

Is there a way to indent the wrapped lines of text in an unordered list? The current layout is not quite what I want, as shown in the first image below. Ideally, I would like it to look more like the second image. I attempted to use margin-left: 56px; and ...

A streamlined method for populating an array with the lengths of words extracted from a text file

After reading a text file with a scanner, I removed punctuation from each line and stored all the words in a single string array. My goal is to track the frequency of word lengths by organizing them into separate arrays based on length. For example, words ...

Is there a way to utilize a MongoDB plugin to retrieve results directly as a return value instead of within a callback function?

I came across a MongoDB plugin for Node.js that always utilizes callback functions to return search results. However, I am looking for a way to receive the result directly without using callbacks. The code snippet below does not provide me with the desire ...

Using jQuery .css({}) is not causing negative margin to function as expected

$('#thankYouMessage').css({"height": textHeight, "margin-top:": "-52px", "padding-left": "19px"}); The CSS property 'padding-left:' will be applied as expected, but the negative margin will not take effect. The 'margin-top:' ...