Transform a hierarchical array data structure into a one-dimensional array structure

Seeking a solution to transform this array of nested objects into a flat array for easier manipulation.

[
  {
    "name": "bill",
    "car": "jaguar",
    "age": 30,
    "profiles": [
      {
        "name": "stacey",
        "car": "lambo",
        "age": 23,
        "profiles": [
          {
            "name": "martin",
            "car": "lexus",
            "age": 34,
            "profiles": []
          }
        ]
      }
    ]
  }
]

The desired result is shown below:

[
  {
    "name": "bill",
    "car": "jaguar",
    "age": 30,
  },{
    "name": "stacey",
    "car": "lambo",
    "age": 23,
  },{
    "name": "martin",
    "car": "lexus",
    "age": 34,
  }
]

Each profiles array can have any number of items, some possibly with empty subarrays of profiles. The converted objects in the array do not include the profiles after transformation.

Considering the use of either underscore or lodash libraries to accomplish this task.

Answer №1

Let's define the original data as o. By combining the power of Array.prototype.reduce with recursion, I have devised the following solution:

o.reduce(function recur(accumulator, curr) {
   var keys = Object.keys(curr);
   keys.splice(keys.indexOf('profiles'), 1);

   accumulator.push(keys.reduce(function (entry, key) {
       entry[key] = curr[key];
       return entry;
   }, {}));

   if (curr.profiles.length > 0) {
       return accumulator.concat(curr.profiles.reduce(recur, []));
   }

   return accumulator;
}, []);

Answer №2

To avoid relying on global variables, I would implement a recursive function that takes the resulting array as a parameter. Here is an example of how this can be done:

var output = [];

function extractData(input, output) {
    for (var j=0; j<input.length; j++) {
        var newData = {
            name: input[j].name,
            car: input[j].car,
            age: input[j].age
        };
        output.push(newData);
        
        if (input[j].profiles instanceof Array &&
            input[j].profiles.length>0)
            extractData(input[j].profiles, output);
    }
}

console.log(output); // displays the elements in a readable format

Please note that this code has not been tested thoroughly, so it is recommended to use it as a reference only.

(edit1: Corrected variable declaration in for loop)

Answer №3

const _ = require('lodash')

const extractArrayFromObject = (inputObj, inputArr) => {
  const {data, ...others} = inputObj
  if (!_.isEmpty(inputObj.data)) {
    return extractArrayFromObject(inputObj.data!, [...inputArr, others])
  }
  return [...inputArr, others]
}

const flattenedArray = extractArrayFromObject(myRecursiveData, [])

Answer №4

Hey there! Give this a shot...

    let result = [];
    let index = 0;

    const extractData = (source, output) => {
        if(source[0] == null){
            index = output.length - 1;
            return false;
        } else { 
          output.push(source[0]);
        }
        extractData(source[0].profiles, output);
        delete output[index--].profiles;
    };

    extractData(inputArray, result); // here 'inputArray' is the input array and 'result' is the output
    console.log(result);

Good luck!

Answer №5

var _ = require('lodash')
/**
 * This function flattens an array object by extracting recursive properties.
 * @see {@link http://example.com/handle-recursive-properties}
 * @param  {Array} arr                The array of objects with recursive properties
 * @param  {String} recursiveProperty The name of the recursive property
 * @return {Array}                    A flat array containing all recursive properties without the specified recursive property
 */
function extractRecursiveProperties (arr, recursiveProperty) {
  var extracted = []
  function _extractRecursive (children) {
    _.each(children, function (item) {
      if (item[recursiveProperty] && item[recursiveProperty].length) _extractRecursive(item[recursiveProperty])
      extracted.push(_.omit(item, recursiveProperty))
    })
  }
  _extractRecursive(arr)
  return extracted
}

module.exports = extractRecursiveProperties

Answer №6

It has been nearly three years and the search for a one-size-fits-all solution continues. Taking inspiration from @axelduch's response, here is a heavily influenced approach.

const {isPlainObject, isArray, get, omit, reduce} = require('lodash')
const recursiveFlatten = (tree, headProp, parentIdProp, parentRefProp, parent = {}) => {
  tree = isArray(tree) ? tree : [tree]
  return reduce(tree, (acq, current) => {
    const currentWithoutHead = omit(current, [headProp])
    if (parentIdProp && parentRefProp) currentWithoutHead[parentRefProp] = parent[parentIdProp] || null
    acq = [...acq, currentWithoutHead]
    const next = get(current, headProp)
    if (isPlainObject(next) || isArray(next)) {
      parent = currentWithoutHead
      acq = [...acq, ...recursiveFlatten(next, headProp, parentIdProp, parentRefProp, parent)]
    }
    return acq
  }, [])
}

Let's consider a basic example:

const example = recursiveFlatten({
  name: 'bill',
  love: true,
  lovers: [{
    name: 'jil',
    love: false,
    lovers: [{
      name: 'diana',
      love: false,
      lovers: false
    }, {
      name: 'einstein',
      love: false,
      lovers: {
        name: 'carl sagan',
        love: false,
        lovers: false
      }
    }]
  }]
}, 'lovers')

[ { name: 'bill', love: true },
  { name: 'jil', love: false },
  { name: 'diana', love: false },
  { name: 'einstein', love: false },
  { name: 'carl sagan', love: false } ]

Now, let's enhance the example by adding a parentId property using parentRef.

const example = recursiveFlatten({
  name: 'bill',
  love: true,
  lovers: [{
    name: 'jil',
    love: false,
    lovers: [{
      name: 'diana',
      love: false,
      lovers: false
    }, {
      name: 'einstein',
      love: false,
      lovers: {
        name: 'carl sagan',
        love: false,
        lovers: false
      }
    }]
  }]
}, 'lovers', 'name', 'parentName')

[ { name: 'bill', love: true, parentName: null },
  { name: 'jil', love: false, parentName: 'bill' },
  { name: 'diana', love: false, parentName: 'jil' },
  { name: 'einstein', love: false, parentName: 'jil' },
  { name: 'carl sagan', love: false, parentName: 'einstein' } ]

Answer №7

Here is a straightforward method that can address the issue as initially described.

const flattenRecursive = (tree) => 
  tree.length == 0
    ? []
    : tree.flatMap(({profiles = [], ...rest}) => [{...rest}, ...flattenRecursive(profiles)])

const treeData = [{name: "bill", car: "jaguar", age: 30, profiles: [{name: "stacey", car: "lambo", age: 23, profiles: [{name: "martin", car: "lexus", age: 34, profiles: []}]}]}, {name: "denise", car: "pinto", age: 28}]

console.log(
  flattenRecursive(treeData)
)

This code snippet specifically targets the property 'profiles' and eliminates it while retaining the other properties in the new object.

Your solution appears to have more intricate requirements. This version accommodates these through multiple optional parameters, similar to your proposed solution, although the invocation process differs here and could be adjusted if needed:

const flattenRecursive = (headProperty, parentIdProperty, parentReferenceProperty, parent = {}) => (tree) => 
  tree.length == 0
    ? []
    : tree.flatMap(({[headProperty]: children = [], ...rest}) => [
        {
          ...rest,
          ...(parentIdProperty && parentReferenceProperty ? {[parentReferenceProperty]: parent[parentIdProperty] || null} : {})
        },
        ...flattenRecursive(headProperty, parentIdProperty, parentReferenceProperty, rest)(children)
      ])
    

const dataTree = [{name: "bill", car: "jaguar", age: 30, profiles: [{name: "stacey", car: "lambo", age: 23, profiles: [{name: "martin", car: "lexus", age: 34, profiles: []}]}]}, {name: "denise", car: "pinto", age: 28}]

console.log(flattenRecursive('profiles')(dataTree))
console.log(flattenRecursive('profiles', 'name', 'parentName')(dataTree))

I might not prefer this particular API structure in my own projects due to the complexity introduced by the varying behaviors based on the number of passed arguments. I would consider encapsulating them within an API like

const flattenRecursive = (parentIdProperty, parentReferenceProperty) => (headProperty) => (tree) => ...

After which we could use specific functions such as

const flatProfiles = flattenRecursive(null, null)('profiles')

and

const flatAndExpand = flattenRecursive('name', 'parentName')('profiles')

to replace the two function calls within the console.log() statements above.

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

Switching visual representation that appears upon clicking the dropdown menu

Issue with Duplicating Dropdown and Image Change const image = document.querySelector('.item__img'); const checkbox = document.querySelectorAll('.imgOption'); function handleChange() { let imgsrc = this.getAttribute("data-value ...

Struggling with navigating through MongoDB's ObjectId using Cypress

I'm facing an issue when trying to run a specific query. db.getCollection('name').find({_id : ObjectId("hereGoesAnId")}) My setup involves using the mongodb module and utilizing cy.task to execute queries on the database. Everythi ...

Display the output based on checkbox selection using JavaScript

I am working on a feature where I need to capture checkbox inputs using JavaScript and then store them in a PHP database. Each checkbox corresponds to a specific column in the database. If a checkbox is checked, I want to insert its value into the databa ...

How to Handle a TypeError When Deleting a File Using GridFS with Mongoose and Node.js

In the process of developing an application that facilitates uploading and downloading music files, I have encountered a specific issue. While I am able to successfully upload files and send them to clients, I am facing challenges when attempting to delete ...

What is the best way to display a 'confirmed' message on my registration page once a user has submitted their information?

I have set up a nodejs server and developed a registration page using HTML. When users click the submit button, the server I built receives the input data and validates it. If the user is successfully added to the database, I want to display a new message ...

What is the best method to transfer text entered in one textarea to the output of another?

I recently picked up JavaScript again and encountered an issue while developing an app. I am struggling to pass input from one textarea to another, where the text should be converted to uppercase. I attempted to tweak code from w3, but I prefer to achieve ...

Send the appropriate data once the response has been completed

My Express.JS server has multiple res.json responses. In order to conduct statistics, logging, and debugging, I am looking to capture the response payload using a universal hook. While I have come across the finish event res.on('finish'), I am s ...

How can I ensure a mock is resolved before rendering in React with Jest?

By default, the loading state in my react component is set to true, and then it changes to false after a fetch call. Within my jsx code, I have a conditional check like {!loading && which functions correctly. However, during testing, the fetch call is mock ...

Guide on integrating cookiejar with HTTP2

I am currently facing a challenge in making http2 requests and incorporating a cookie jar or container in node.js. Despite researching online, I have not yet found a suitable solution. If, for instance, I wish to send the following http2 request: const cl ...

What is the process for running child_process when a user clicks on a view in an application

Just starting out with Node.js and utilizing express along with hogan or moustache templating for my views. I've successfully used the following code in my routing files, index.js as shown below: /* Test Shell Execute. */ router.get('/shell&apo ...

Tips on incorporating variable tension feature into D3 hierarchical edge bundling

I found a d3 sample on hierarchical edge bundling that I am experimenting with - My main focus is how to add tension functionality to the example provided at the following link (code can be found here): While I have reviewed the code in the first link, I ...

What is the best method to determine the accurate height of a window that works across all browsers and platforms?

Is there a way to accurately determine the visible height of the browser window, taking into consideration any floating navigation bars or bottom buttons that may interfere with the actual viewing area? For example, mobile browsers often have floating bar ...

Having trouble with SCSS in Angular 4 CLI after adding new CSS styles?

After successfully creating a fresh Angular project using the Angular CLI for Angular 4, I decided to generate it with SCSS: ng new myproject --style=sass Everything went smoothly until I attempted to add some CSS code. For instance, when I added the fo ...

Utilizing a flat array to retrieve information from a multidimensional array

I'm currently grappling with a perplexing issue that seems to be just out of reach: My goal is to create a 1D array to mimic the behavior of a 3D array. The way I am initializing my array is as follows: int *cache = new int[width*heigh*depth]; and ...

Verifying whether an ng-repeat returns an empty set following a filter operation

I am attempting to achieve the following: Display a list of approximately 50 items using ng-repeat Filter the list based on a text field (ng-repeat="note in notes|filter:basicFilter") If the basic filter returns no results (or a small number, like 5), ma ...

The template failed to load on AngularJS post-compilation

Following the build of my Angular app, I encountered this error message: [$compile:tpload] Failed to load template: app/app.html (HTTP status: 404 Not Found). Seeking assistance from you all!!!!! app.html <div class="root"> <div ui-view> ...

Typescript is throwing an error stating that the type 'Promise<void>' cannot be assigned to the type 'void | Destructor'

The text editor is displaying the following message: Error: Type 'Promise' is not compatible with type 'void | Destructor'. This error occurs when calling checkUserLoggedIn() within the useEffect hook. To resolve this, I tried defin ...

Do PHP arrays get duplicated as values or as references when assigned to new variables and when they are passed to functions?

1) Is an array passed as an argument to a method or function by reference or by value? 2) When you assign an array to a variable, does the new variable act as a reference to the original array, or is it a new copy? What happens in this scenario: $a = a ...

Using relative paths to showcase images in Node.js

I am currently working on my first Node.js MVC app where I am using native Node and not utilizing Express. One issue I am facing is the difficulty in displaying images from my HTML files through their relative paths. Instead of sharing my server.js and ro ...

Organizing controllers in separate files within AngularJS

I've been working on modularizing an AngularJS project by implementing different controllers in their own JS files. However, I'm facing issues with getting them to work properly. Below is my config.js file where the primary module is defined: ( ...