Transform a plain array to a nesting array when only having parent ID

Viewed 225

The parent id represents another category id which is its parent. With the dataset below I try to accomplish programmatically a structure like this:

| Cat Top Level 1
| Cat Top Level 2
| Cat Top Level 3
| --- Cat Top 3 Child 1
| --- Cat Top 3 Child 2
| ------ Cat Top 3 Child 2 Child 1
| --------- Cat Top 3 Child 2 Child 1 Child 1
| --- Cat Top 3 Child 3

The objects need to be merged into one and other. If a 'child' category is pushed into its direct 'parent' category it must come in a newly to create property called 'children' (array).

Dataset:

const categories = [
  {
    id: 16,
    name: "Cat Top Level 1",
    parent: 0,
  },
  {
    id: 17,
    name: "Cat Top Level 2",
    parent: 0,
  },
  {
    id: 18,
    name: "Cat Top Level 3",
    parent: 0,
  },
  {
    id: 19,
    name: "Cat Top 3 Child 1",
    parent: 18,
  },
  {
    id: 20,
    name: "Cat Top 3 Child 2",
    parent: 18,
  },
  {
    id: 22,
    name: "Cat Top 3 Child 2 Child 1",
    parent: 20,
  },
  {
    id: 23,
    name: "Cat Top 3 Child 2 Child 1 Child 1",
    parent: 22,
  },
  {
    id: 21,
    name: "Cat Top 3 Child 3",
    parent: 18,
  },
  {
    id: 15,
    name: "Uncategorized",
    parent: 0,
  },
];

The code that I came up with so far goes like this.

function mergeCategories(categories) {
  categories.map((category) => {
    category.children = [];
    if (category.parent === 0) {
      return;
    }
    categories.map((subCategory) => {
      if (subCategory.id !== category.parent) {
        return;
      }
    });
  });
}

Loop in a loop will return many duplicates. A .filter() seems to be needed as well, I guess. Due to performance .forEach() is excluded.

It is by far not complete, and does not work, of course. My mind is going crazy when I'm thinking of how to approach this. I thought It would be simple. But it's quite hard to be honest.

2 Answers

You could do this using recursive approach with reduce method that will check if the pid parameter is equal to id of the current element and if so add that object to the accumulator value. Then you also create recursive call to get children and if there is children found you add them as children property to current object. This will also expect that your root objects have parent value of 0.

const data = [{"id":16,"name":"Cat Top Level 1","parent":0},{"id":17,"name":"Cat Top Level 2","parent":0},{"id":18,"name":"Cat Top Level 3","parent":0},{"id":19,"name":"Cat Top 3 Child 1","parent":18},{"id":20,"name":"Cat Top 3 Child 2","parent":18},{"id":22,"name":"Cat Top 3 Child 2 Child 1","parent":20},{"id":23,"name":"Cat Top 3 Child 2 Child 1 Child 1","parent":22},{"id":21,"name":"Cat Top 3 Child 3","parent":18},{"id":15,"name":"Uncategorized","parent":0}]

function toNested(data, pid = 0) {
  return data.reduce((r, e) => {
    if (pid == e.parent) {
      const object = { ...e }
      const children = toNested(data, e.id);

      if (children.length) {
        object.children = children
      }

      r.push(object)
    }

    return r;
  }, [])
}

const result = toNested(data);
console.log(result)

Have an addChild method, which will find the parent (recursively) and appends to children.

const tree = { id: 0, name: "root", children: [] };

const addChild = (obj, parent) => {
  if (obj.parent === parent.id) {
    parent.children.push({...obj, children: []});
  } else {
    parent.children.forEach((item) => addChild(obj, item));
  }
};
const buildTree = (arr) => arr.forEach(obj => addChild(obj, tree));

const categories = [{"id":16,"name":"Cat Top Level 1","parent":0},{"id":17,"name":"Cat Top Level 2","parent":0},{"id":18,"name":"Cat Top Level 3","parent":0},{"id":19,"name":"Cat Top 3 Child 1","parent":18},{"id":20,"name":"Cat Top 3 Child 2","parent":18},{"id":22,"name":"Cat Top 3 Child 2 Child 1","parent":20},{"id":23,"name":"Cat Top 3 Child 2 Child 1 Child 1","parent":22},{"id":21,"name":"Cat Top 3 Child 3","parent":18},{"id":15,"name":"Uncategorized","parent":0}]


buildTree(categories);
console.log(tree);

Related