How to convert flat array containing a child "path" array into a nested array of objects based on the path?

Viewed 259

I have an array of objects, each containing a path property which holds the value of "paths" to which I'd like to map the array elements to.

let myData = [
  {
    path: ['Movies', 'Comedies', 'TopRanked'],
    name: 'The Hangover',
    id: '1',
  },
  {
    path: ['Movies', 'Comedies', 'TopRanked'],
    name: 'Eurotrip',
    id: '2',
  },
  {
    path: ['Movies', 'Action'],
    name: 'Need for Speed',
    id: '3',
  },
  {
    path: ['Life'],
    name: 'Not so bad',
    id: '4',
  },
  {
    path: ['Life', 'Financial', 'Income'],
    name: 'Making Hundreds',
    id: '5',
  },
  {
    path: ['Life', 'Financial', 'Income'],
    name: 'Making Thousands',
    id: '6',
  },
  {
    path: ['Life', 'MonthlySpent'],
    name: 'Just a little bit',
    id: '7',
  },
  {
    path: ['Life', 'MonthlySpent'],
    name: 'Living large',
    id: '8',
  },
];
console.log(myData);

Essentially, the result I am looking for is a breakdown of that array into as many as nested arrays as needed (relative to all possible available paths), with each retaining its "type" - either a parent or an item. So the desired output is like so:

let myTree = [
  {
    name: 'Movies',
    type: 'parent',
    children: [
      {
        name: 'Comedies',
        type: 'parent',
        children: [
          {
            name: 'TopRanked',
            type: 'parent',
            children: [
              {
                name: 'The Hangover',
                type: 'item',
                id: 1,
                path: ['Movies', 'Comedies', 'TopRanked']
              },
              {
                name: 'Eurotrip',
                type: 'item',
                id: 2,
                path: ['Movies', 'Comedies', 'TopRanked'],
              }
            ]
          },
        ]
      },
      {
        name: 'Action',
        type: 'parent',
        children: [
          {
            name: 'Need for Speed',
            type: 'item',
            id: 3,
            path: ['Movies', 'Action'],
          },
        ]
      }
    ]
  },
  {
    name: 'Life',
    type: 'parent',
    children: [
      {
        name: 'Not so bad',
        type: 'item',
        id: 4,
        path: ['Life'],
      },
      {
        name: 'Financial',
        type: 'parent',
        children: [
          {
            name: 'Income',
            type: 'parent',
            children: [
              {
                name: 'Making Hundreds',
                type: 'item',
                id: 5,
                path: ['Life', 'Financial', 'Income'],
              },
              {
                name: 'Making Thousands',
                type: 'item',
                id: 6,
                path: ['Life', 'Financial', 'Income'],
              }
            ]
          }
        ]
      },
      {
        name: 'MonthlySpent',
        type: 'parent',
        children: [
          {
            name: 'Just a little bit',
            type: 'item',
            id: 7,
            path: ['Life', 'MonthlySpent'],
          },
          {
            name: 'Living Large',
            type: 'item',
            id: 8,
            path: ['Life', 'MonthlySpent'],
          }
        ]
      }
    ]
  }
]
console.log(myTree);

I tried the following, and while the tree structure is created, the "item"-types are not placed as the array-value of the last nested "parent" type:

function treeData(data) {
    var result = [],
        hash = { _: { children: result } };

    data.forEach(function (object) {
      object.path.reduce(function (o, p) {
        if (!o[p]) {
          o[p] = { _: { name: p, children: [] } };
          o._.children.push(o[p]._);
        }
        return o[p];
      }, hash)._.name = object.name;
    });
    return result;
  }

Would appreciate a working solution, as I am wracking my head and can't find one. Tnnx.

1 Answers

The approach below follows a similar pattern to your code i.e. loop every object, but instead of a reduce simply loops every item in path and creates a branch off the root. When there are no more 'branches' then add the original object. See the comments.

let myData = data();
let myTree = treeData(data);
console.log(myTree);

function treeData(data) {
  let root = {"children": []} // create origin
  for (obj of myData) { // loop items in the data
    obj.type = "Item"; // add a property to suit your output
    let tree = root; // start at root every object
    for (path of obj.path) { // loop over items in path
      let branch = tree.children.find(k => k.name == path); // look for branch
      if (!branch) { // if no branch, create one
        branch = {"name": path, "type": "parent", "children": []}
        tree.children.push(branch); // push this into children of current level
      }
      tree = branch; // set tree to branch before processing next item in path
    }
    tree.children.push(obj); // add the item to the hierarchy after path is exhausted
  }
  return root.children; // return children of the root to suit your output
}

function data() {
  return [
    {
      path: ['Movies', 'Comedies', 'TopRanked'],
      name: 'The Hangover',
      id: '1',
    },
    {
      path: ['Movies', 'Comedies', 'TopRanked'],
      name: 'Eurotrip',
      id: '2',
    },
    {
      path: ['Movies', 'Action'],
      name: 'Need for Speed',
      id: '3',
    },
    {
      path: ['Life'],
      name: 'Not so bad',
      id: '4',
    },
    {
      path: ['Life', 'Financial', 'Income'],
      name: 'Making Hundreds',
      id: '5',
    },
    {
      path: ['Life', 'Financial', 'Income'],
      name: 'Making Thousands',
      id: '6',
    },
    {
      path: ['Life', 'MonthlySpent'],
      name: 'Just a little bit',
      id: '7',
    },
    {
      path: ['Life', 'MonthlySpent'],
      name: 'Living large',
      id: '8',
    },
  ];
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related