Generating Tree Structure Object Automatically from Collection using Keys

I am looking to automatically generate a complex Tree structure from a set of objects, with the levels of the tree determined by a list of keys.

For example, my collection could consist of items like

[{a_id: '1', a_name: '1-name', b_id: '2', b_name: '2_name', c_id: '3', c_name: '3_name'}...]

The specified keys for each level of the tree could be something like: ['a', 'b', 'c']

In this setup, the top-level object contains an options property that holds pairs of {key: any, value: any}. Each child object also has the same structure, but within an array named groups, where each group has a group attribute referring to its parent option's value.

The desired output for the Tree object would resemble:

{
  id: 'a',
  options: [{ key: '1-name', value: '1'}...],
  child: {
    id: 'b',
    groups: [
      { group: '1', name: '1-name', options: [{key: '2-name', value: '2'}]}
    ],
    child: {
      id: 'c',
      groups: [
        { group: '2', name: '2-name', options: [{key: '3-name', value: '3'}]}
      ],
    }
  }
}

I am struggling to create a concise function that can construct this recursive structure using the basic collection. I aim for the keys to determine the nested relationships and organize the corresponding objects as recursive children. If there is a better approach to transform the original collection into the target structure, I am open to suggestions.

Answer №1

function organizeData(keys, information, parentGroup, parentName) {
  if (keys.length == 0) return;
  var currentKey = keys.shift(); // Extract first element and remove it from array
  var finalResult = {id: currentKey};
  var groupValue = information[currentKey + "_id"];
  var nameValue = information[currentKey + "_name"];
  var dataOptions = [{key: nameValue, value: groupValue}];
  if (parentGroup) { // Only null for first level
    finalResult.groups = [{
      group: parentGroup,
      name: parentName,
      options: dataOptions
    }];
  } else {
    finalResult.options = dataOptions;
  }
  if (keys.length > 0) { // Check if not the last key in the array
    finalResult.child = organizeData(keys, information, groupValue, nameValue);
  }
  return finalResult;
}

var organizedData = organizeData (
  ['a', 'b', 'c'],
  {a_id: '1', a_name: '1-name', b_id: '2', b_name: '2_name', c_id: '3', c_name: '3_name'}
);

console.log(organizedData);

See a demo at https://codepen.io/bortao/pen/WNvdXpy

Answer №2

A new class was designed to handle the entire collection and includes a method for exporting data in the specific format requested in the initial query:

import { Option, OptionGroup, SelectTreeFieldOptions } from '@common/ui';
import { isObject } from 'lodash';

// Defining Symbol for Key in Group
const KEY_SYMBOL = Symbol('key');

/**
 * The Tree class creates deeply nested objects based on the specified 'level'
 */
export class Tree {

  private readonly level: string;
  private readonly groups: Record<string, Record<string, any>>;
  private readonly child?: Tree;

  constructor(private readonly levels: string[], private readonly isRoot = true) {
    this.level = levels[0];
    this.groups = isRoot && { root: {} } || {};
    if (levels.length > 1) {
      this.child = new Tree(levels.slice(1), false);
    }
  }

  /**
   * Populates the Tree with the provided Collection of models
   * Each model should have two properties per level: ${level}_id and ${level}_name
   * Entries are matched to the corresponding level's groups and passed down to its children
   * @param data - the data Collection used to populate the Tree
   */
  setData(data: Record<string, any>[]): Tree {
    data.forEach(entry => this.addData(entry, 'root', ''));
    return this;
  }

  export() {
    const key = this.isRoot && 'options' || 'groups';
    const values = this.getOptionsOrGroups();
    return {
      id: `${this.level}s`,
      [key]: values,
      ...(this.child && { child: this.child.toSelectTreeField() } || {}),
    };
  }

  /**
   * Retrieves recursive Options or OptionGroups from each level of the Tree
   * @param nestedObject - optional sub-object found on a child branch of the Tree
   */
  protected getOptionsOrGroups(nestedObject?: Record<string, any>): (Option | OptionGroup)[] {
    const options = (this.isRoot && this.groups.root) || nestedObject || this.groups;
    return Object.entries(options).map(([val, key]) => {
      const value = Number(val) || val;
      if (!isObject(key)) {
        return { key, value };
      }
      return { value, group: key[KEY_SYMBOL], options: this.getOptionsOrGroups(key) };
    }) as (Option | OptionGroup)[];
  }

  /**
   * Attempts to add data from a collection item to the current level of the Tree
   * @param data - the collection item
   * @param parentId - ID of the parent item if this Tree is a child
   * @param parentName - label used for the parent item if this Tree is a child
   */
  protected addData(data: Record<string, any>, parentId: string, parentName: string) {
    const id = data[`${this.level}_id`];
    const name = data[`${this.level}_name`];
    if (!id || !name) {
      throw new Error(`ID or Name not found on level ${this.level}`);
    }
    (this.groups[parentId] || (this.groups[parentId] = { [KEY_SYMBOL]: parentName }))[id] = name;
    if (this.child) {
      this.child.addData(data, id, name);
    }
  }
}

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

Using dot notation for event handlers in Vue.Js is a handy technique

I am currently developing a Single Page Application (SPA) using Vue.js 3 and Bootstrap 5. On the main page, I have implemented the Bootstrap Offcanvas element with code that closely resembles the one provided in the documentation. The structure of the off ...

The default action is not triggered when the click event occurs

Hey there, I have been working on this <ol> list of events using jQuery (version 1.4.2). Everything is set up within the $(document).ready() function. Something strange is happening where when I click on the <li>, it triggers a click on the co ...

Ensure that a synchronous action is performed prior to each Backbone HTTP request

For each authenticated request (GET, POST, etc) in my Backbone/Marionette application, it is necessary to include an accessToken. The accessToken and expireDate are stored in the localStorage. To verify if the accessToken has expired, I utilize the metho ...

Error 500 on Firebase: Issue solving "firebase" in "firebase.js" not resolved

Struggling to incorporate Firebase into my latest React project, I keep encountering the dreaded "The development server returned response error code: 500." Despite creating a firebase.js file to house my Firebase configuration details, I am at a loss as ...

Encountering an "undefined is not a function" error across all libraries

While working on ASP.Net MVC4, I have encountered an issue where I consistently receive the error message "undefined is not a function" when using jQuery functions with different libraries. Despite ensuring that every ID is correct and everything has bee ...

What is the method for executing a specific task using grunt.registerTask()?

My grunt tasks are registered like this - grunt.registerTask('regenerateSources', ['clean:local', 'bower', 'uglify']); When I run grunt regenerateSources, it executes the clean, bower and uglify tasks. But is there ...

PHP Filter Function to Retrieve Items Based on Multiple Key Values with OR Logic

I have been searching high and low for a solution to this problem, but I'm not entirely sure if it's even possible. Hopefully someone can provide some guidance in the right direction. Currently, I am retrieving a multidimensional array from MySQ ...

Redirecting from HTTP to HTTPS with node.js/Express

Are there steps I can take to modify my web application to operate on HTTPS instead of HTTP using node.js/express? I require it to run on HTTPS due to the use of geolocation, which Chrome no longer supports unless served from a secure context like HTTPS. ...

Is it possible for me to avoid html tags inside a class without using the xmp tag?

There are a few ways to approach this question. It's important to decide which method will be most beneficial for your specific needs... Is it possible for JavaScript to recreate the deprecated <xmp> tag using an "xmp" class? Can we replicate ...

Encountering a syntax error with the spread operator while attempting to deploy on Heroku

I'm encountering an error when attempting to deploy my app on Heroku: remote: SyntaxError: src/resolvers/Mutation.js: Unexpected token (21:16) remote: 19 | const user = await prisma.mutation.createUser({ remote: 20 | data: { r ...

Please reset the form fields after the most recent edit

I've created a form that includes multiple select elements. When an option is selected, it activates the next select element and updates it with values using Ajax and PHP. However, I'm facing an issue where changing a previous option only resets ...

On paste events, CKEditor will strip away all HTML tags except for div and span elements

I am trying to implement a feature in CKeditor where all HTML tags are removed when a user pastes content using Ctrl+v. The code I have written so far does not seem to work as expected. <script type="text/javascript"> CKEDITOR.on('instanceR ...

When using angular $resource.save for savings, the view is forced to redraw and reflow because of the collection watcher

One of the challenges I'm facing involves loading and storing a model using $resource in AngularJS. This particular model is an aggregate with nested collections, which are displayed in an HTML view using ng-repeat. The structure of the model looks l ...

Adjust the size of every card in a row when one card is resized

At the top of the page, I have four cards that are visible. Each card's height adjusts based on its content and will resize when the window size is changed. My goal is to ensure that all cards in the same row have equal heights. To see a demo, visit: ...

The JSON response did not contain the expected property in Javascript

Currently, I am developing a weather widget using React that displays temperature and rain forecast. The data is fetched from OpenWeather API and the JSON response structure is as follows: //rainy day 0:{ main: { temp:10} rain: { 3h: 1000} } / ...

How to successfully close a jQuery dialog within a user control

My user control has a list view with a hyperlink (LnkDelete) that triggers a jQuery dialog. Here is the javascript code responsible for this functionality: $('#LnkDelete').live('click', function (e) { var page = $(this).at ...

What is the most efficient way to utilize a single connection to interact with MongoDB?

I have a series of functions that are responsible for running various queries. Here is the code I am working with: var MongoClient = require('mongodb').MongoClient; async function createDatabase() { MongoClient.connect(urlMongoDB, function(er ...

What is the best way to implement bypassSecurityTrustResourceUrl for all elements within an array?

My challenge is dealing with an array of Google Map Embed API URLs. As I iterate over each item, I need to bind them to the source of an iFrame. I have a solution in mind: constructor(private sanitizer: DomSanitizationService) { this.url = sanitizer. ...

Error encountered with the Schema in expressjs and mongoose frameworks

I am currently working on integrating mongoDB with an expressjs project using mongoose, and I have encountered a specific error. throw new mongoose.Error.MissingSchemaError(name); ^ MissingSchemaError: Schema hasn't been registered for model ...

Tips for relocating a CSS button

I have designed two buttons using css and I am looking to align them below the title "forecast customer activity". Due to extensive styling in css, the code might appear lengthy. Requesting assistance with a solution after reviewing the following code snip ...