Utilize the power of JavaScript to recursively map object keys

I am working with an array of objects that have varying depths. My goal is to output the names in a specific format:

Top Level
Top Level > Sub 1
Top Level > Sub 1 > Sub 1-2
Top Level > Sub 2

Unfortunately, I am only able to get the name of the lowest element. I'm not sure what changes I need to make in order to correctly format the names.

        parseCategories(categories, stackCounter = 0) {
          categories.forEach(c => {
            this.res[stackCounter] = { name: c.attributes.name, id: c.id };
            if(c.children.length >= 1) {
              this.parseCategories(c.children, stackCounter + 1);
            }
          });
          return this.res;
        }

The structure of my objects is as follows:

{
   "data":[
      {
         "type":"categories",
         "id":"1",
         "attributes":{
            "name":"Top Level"
         },
         "children":[
            {
               "type":"categories",
               "id":"2",
               "attributes":{
                  "name":"Sub 1"
               },
               "children":[
                  {
                     "type":"categories",
                     "id":"4",
                     "attributes":{
                        "name":"Sub 1-2"
                     },
                     "children":[

                     ]
                  }
               ]
            },
            {
               "type":"categories",
               "id":"3",
               "attributes":{
                  "name":"Sub 2"
               },
               "children":[

               ]
            }
         ]
      }
   ]
}

Answer №1

When dealing with complex traversals, generator functions can offer a simplified solution. By creating a straightforward generator and then wrapping it in a function to generate an array, the process becomes more streamlined:

const getPaths = function * (xs, ps = []) {
  for (let x of xs) {
    yield [... ps, x .attributes .name] .join (' > ')
    yield * getPaths(x .children, [...ps, x .attributes .name])
  }
}

const categoryNames = (categories) => 
  [... getPaths (categories .data)]

const categories = {data: [{type: "categories", id: "1", attributes: {name: "Top Level"}, children: [{type: "categories", id: "2", attributes: {name: "Sub 1"}, children: [{type: "categories", id: "4", attributes: {name: "Sub 1-2"}, children: []}]}, {type: "categories", id: "3", attributes: {name: "Sub 2"}, children: []}]}]};\

console .log (
  categoryNames(categories)
)

getPaths could be enhanced by removing the join (' > ') call and incorporating it within a map call at the end of categoryNames. However, since the specifics surrounding children and attributes.names are quite tailored to this problem, making it more generic may not be necessary.


Clarification

If you found the provided code confusing, here's an explanation to help clarify it. Please bear with me if some parts are already clear to you. Let's break it down:

The main function, categoryNames, simply acts as a wrapper around getPaths, which does most of the heavy lifting.

Two key aspects to note about getPaths:

  • It is a generator function, indicated by the * between the function keyword and its arguments. This allows it to create Generator objects that adhere to the iterable protocol, enabling usage in constructs like let x of generator and [...generator]. Through this, categoryNames converts the output of getPaths into an array. Generator functions operate by yielding individual values or using yield * anotherGenerator to produce each value yielded by another generator separately. The function gets suspended between these yield calls until the next value request.

  • It is a recursive function; the function body invokes itself again with simpler parameters. Typically, recursive functions include an explicit base case where a direct answer is returned without further recursion when the input simplifies sufficiently. Here, the base case is implicit - when xs becomes an empty array, the loop body never triggers, halting the recursion.

getPaths takes a list of values (

xs</code commonly represents such lists) and an array of strings representing current hierarchy paths up to the node being processed. For instance, this might contain <code>["Top Level", "Sub 1"]
. Note that the latter is a default parameter; if left unspecified, it defaults to an empty array.

The function loops over supplied values. For each one, it yields a result by combining current paths with the name property from the attribute property of the current object, separated by " > ". It then recursively processes the child nodes, yielding each child's children sequentially while maintaining the path records. A slightly optimized and clearer version of this approach could look like:

const getPaths = function * (xs, paths = []) {
  for (let x of xs) {
    const newPaths = [... paths, x .attributes .name]
    yield newPaths .join (' > ')
    yield * getPaths (x .children, newPaths)
  }
}

Alternatively, you could define newPaths as follows:

    const newPaths = paths .concat (node .attributes .name)

I hope this breakdown provides clarity. If you have any questions, feel free to leave a comment.

Answer №2

Take a look at this section

categories.forEach(c => {
  this.res[stackCounter] = { name: c.attributes.name, id: c.id };
});

It is evident that res[stackCounter] gets overwritten by the last element of categories every time. To resolve this issue, res[stackCounter] should also be an array.

parseCategories(categories, stackCounter = 0) {
  // initializing res[stackCounter] as an empty array
  if (this.res[stackCounter]) {
    this.res[stackCounter] = [];
  }
  categories.forEach(c => {
    this.res[stackCounter].push({ name: c.attributes.name, id: c.id }); // adding to res[stackCounter]
    if(c.children.length >= 1) {
      this.parseCategories(c.children, stackCounter + 1);
    }
  });
  return this.res;
}

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

What method is the most effective for preloading images and stylesheets?

One of the main concerns I have is optimizing the load time of my website. I would like to preload images, style sheets, and scripts to ensure a faster loading speed and to prevent users from seeing images loading right before them. Can someone suggest the ...

I lose my user interface after a series of clicking events

I've created a React application that generates new quotes without repetition, but I'm encountering an issue where the UI disappears after clicking enough times. What could be causing this behavior in my code? The console displays an object error ...

Unable to modify a record using the 'findById' method and save() function while utilizing promises

I am in the process of developing a cinema application using Node.js and MongoDB with Mongoose. One specific requirement I have is to update the 'Show' model when a user places an order. The task involves adding the latest seats that were ordere ...

Enclose elements in a div using a jQuery each function

I have successfully parsed data from an XML feed to JSON, but now I want to wrap each part of the data within a div. Here is the code snippet I am working with: var newsDiv = $('<div class="news-feed">').appendTo($("body")); $(release ...

"Using a triangular background shape in JavaScript instead of a traditional circular

I want to add a unique effect to my site inspired by the AnimatedHeaderBackgrounds demo available at this link. My twist on this effect involves using upward-moving triangles instead of circles. I've explored various resources, including Stack Overfl ...

Is it possible to convert a .gif file into a jpeg image format for mobile display on my Gatsby website?

I'm looking to convert a .gif file into a mobile.png image for my Gatsby site, but I'm struggling to find the right resources. Does anyone have suggestions on how I can achieve this? Here is the frame: ...

What is the easiest method to transform an input[type=file] into a base64 string in a Vue application using JavaScript?

I am looking to create a base64 string using an input type=file in order to store only the base64 string and make managing images in a database easier. This approach makes it simpler for me to work with just JSON. Here is what I have in my Vue server (u ...

Guide on adding a new member to Mailchimp through node.js and express

Hello, I've been delving into working with APIs, particularly the mail-chimp API. However, I've encountered a problem that has me stuck: const express=require("express"); const bodyparser=require("body-parser"); const request=require("request" ...

Stopping Vue from endlessly looping

There are three fields to consider: hour, minutes, and total. If the hour or minutes change, I want to calculate the total. If the total is changed, I want to calculate the corresponding minutes and hours. For example: 1h 30minutes = 1.5 Total 2.25 To ...

Struggling to fetch a custom attribute from the HTML Option element, receiving [object Object] as the result instead

I've been facing a challenging issue all day. My task involves making an ajax call to a predefined JSON file and trying to save certain contents into option tags using custom attributes. However, every time I attempt to retrieve the data stored in the ...

Tips for running two elixir tasks consecutively?

Check out this piece of code: var gulp = require('gulp'), fs = require('fs'); gulp.task('taskOne', function() { return gulp.src('folder1/file1.js') .pipe(gulp.dest('folder2')); }); gulp.t ...

Top recommendation for utilizing $scope variables in Angular applications

Currently, I am working on a new project and I want to ensure that I am correctly utilizing $scope. After watching an informative video on best practices, Miško mentioned that manipulating $scope properties directly may not be the best approach. Typical ...

Change the color of the menu icon based on the specified HTML class or attribute

I'm trying to create a fixed menu that changes color depending on the background of different sections. Currently, I am using a data-color attribute but I am struggling with removing and adding the class to #open-button. Adding the class works fine, ...

Use jQuery to apply a class to some input elements when certain events like keyup or

If I type something in the input field, it should add a border to the li tag containing the text. The current script works fine, but is there a way to make this jQuery script shorter? Thank you for your help! .add_border{ border: 2px solid #000 !impor ...

Sending POST Requests with Node and ExpressJS in the User Interface

Just diving into the world of Node.js and Express.js, I'm attempting to create a form submission to an Express.js backend. Below is a snippet of the code I am working with: var author = 'JAck'; var post = 'Hello World'; var body ...

Using Jquery to retrieve data in sections from a server and continuously add it to a file on the client side

I have a large dataset stored in JSON format on a Postgres Server with hundreds of thousands of rows. To prevent memory overload on the server, I need to provide users with the ability to download the data in chunks rather than all at once. This requires a ...

Request financial data from AlphaVantage using PHP

I'm currently working on utilizing the alphavantage API to retrieve currency exchange information. In order to obtain the desired data, I am using the following query URI: https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_cu ...

Issue with Vue.js 2.0 transition not triggering on changing routes dynamically

I've encountered an issue where transitions are not firing on dynamic routes with parameters. For example, when navigating from /chapter/1 to /chapter/2, no transition occurs. However, when going from /chapter/1 to /profile/1, a transition does occur! ...

The Render function in ReactJS is not getting refreshed

My goal is to implement a chat feature using material UI. I have set up a function where users can submit new chat messages, which then go through the reducer and are stored in the redux store. The process seems to be working fine, except for the fact that ...

Accessing information from RESTful Web Service with Angular 2's Http functionality

I am currently working on retrieving data from a RESTful web service using Angular 2 Http. Initially, I inject the service into the constructor of the client component class: constructor (private _myService: MyService, private route: Activat ...