Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 214
Niklas Fondberg Avatar asked Jul 09 '21 13:07

Niklas Fondberg


Video Answer


2 Answers

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 });
like image 187
Wyck Avatar answered Oct 10 '22 03:10

Wyck


Assuming that the root is always the same I came up with this code, it took me some time but it was fun to think about it.

var data = {};

list.forEach(item => {
  var path = item.path.split("/");
  
  let parent = data;
  
  path.forEach((id) => {
    if (!parent.id) {
      parent.id = id;
      parent.children = [];

      if (id != item.id) {
        let next = {}
        parent.children.push(next);
        parent = next;        
      }

    } else if (parent.id != id) {
      let next = parent.children.find(child => child.id == id);

      if (!next) {
        next = { id: id, children: [] }
        parent.children.push(next);
      }
      
      parent = next;
    }
  });
  
  parent.id = item.id;
  parent.name = item.name
});

output:

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

I think that having more roots here may need some fixing. Although I think the problem would be different if we were talking about multiple roots since your data variable is an object

Also, if you think in a recursive way it can be more understandable, but no comments on performance.

like image 36
Daniel Aragão Avatar answered Oct 10 '22 03:10

Daniel Aragão