Dynamically create multidimensional array from split input



I have an array of ojects which all have a path and a name property. Like

[
{
 "id": "1",
 "path": "1",
 "name": "root"
},
{
 "id": "857",
 "path": "1/857",
 "name": "Animals"
},
{
 "id": "1194",
 "path": "1/857/1194",
 "name": "Dinasours"
},
...and so on
]

Here are some path examples

1/1279/1282
1/1279/1281
1/1279/1280
1/857
1/857/1194
1/857/1194/1277
1/857/1194/1277/1278

I want to turn this into a multidimensional array like:

const data = {
  id: "1",
  name: "Root",
  children: [
    {
      id: "1279",
      name: "Toys",
    },
    {
      id: "857",
      name: "Animals",
      children: [
        {
          id: "1194",
          name: "Dinasours",
          children: [
            {
              id: "1277",
              name: "T-Rex",
              children: [
                {
                  id: "1278",
                  name: "Superbig T-Rex",
                },
              ],
            },
          ],
        },
      ],
    },
  ],
};

As you can understand the amount of data is much larger.

Is there a neat way to transform this data?

Answer

I wonder if this would be sufficient for your needs?

I’ll refer to the objects as nodes (just because I’m a graph theory person, and that’s how I roll).

  1. Build an index mapping each id to the object itself using a Map. (Purely for efficiency. You could technically find each node from scratch by id each time you need it.)
  2. Split the path to obtain the second last path fragment which should be the id of the direct parent of the node. (Assuming there’s only one and that there is guaranteed to be a node corresponding to that id?)
  3. Add the child to the parent’s list of children. We’ll be careful not to add it multiple times.

This will result in nodes that have no children literally having no children property (as opposed to having a children property that is just []). I also did not remove/delete the path property from the objects.

As a note of caution, if there are path fragments that do not have corresponding objects, this will not work.

const nodes = [
  { id: '1', path: '1', name: 'root' },
  { id: '857', path: '1/857', name: 'Animals' },
  { id: '1194', path: '1/857/1194', name: 'Dinasours' }
  //...and so on
];

const index = new Map();
for (let node of nodes) {
  index.set(node.id, node)
}
for (let node of nodes) {
  const fragments = node.path.split('/');
  const parentId = fragments[fragments.length - 2];
  const parent = index.get(parentId);
  if (parent !== undefined) {
    parent.children = parent.children || [];
    if (!parent.children.includes(node)) {
      parent.children.push(node);
    }
  }
}

// TODO: Decide which node is the root.
// Here's one way to get the first (possibly only) root.
const root = index.get(nodes[0].path.split('/')[0]);

console.dir(root, { depth: null });


Source: stackoverflow