Advanced JS

Convert Lists to Trees

Convert flat JavaScript lists into trees, flatten nested items, and transform category data into lookup-friendly structures.

Transform Lists and Nested Structures

Many APIs return flat records, while UI components often need nested trees. The examples show both directions: build a tree from id/parentId, flatten it back to a list, and flatten nested category data into rows with parent metadata.

List to Tree

This utility transforms a flat list of items with id and parentId fields into a nested tree. A Map stores every node by id first, then the second pass attaches each node to its parent.

The same section includes treeToList(), which flattens the tree back into records for persistence, normalization, or sending edited tree data back to an API.

function listToTree(items) {
  const map = new Map();
  const roots = [];

  // Map items by ID
  for (const item of items) {
    map.set(item.id, { ...item, children: [] });
  }

  for (const item of items) {
    const node = map.get(item.id);

    if (item.parentId === null) {
      roots.push(node);
    } else {
      const parent = map.get(item.parentId);

      if (parent) {
        parent.children.push(node);
      }
    }
  }

  return roots;
}

const items = [
  { id: 1, name: "Root 1", parentId: null },
  { id: 2, name: "Child 1.1", parentId: 1 },
  { id: 3, name: "Child 1.2", parentId: 1 },
  { id: 4, name: "Root 2", parentId: null },
  { id: 5, name: "Child 2.1", parentId: 4 },
  { id: 6, name: "SubChild 2.1.1", parentId: 5 },
];

const tree = listToTree(items);
console.log(tree);
// [
//   {
//     "id": 1,
//     "name": "Root 1",
//     "parentId": null,
//     "children": [
//       {
//         "id": 2,
//         "name": "Child 1.1",
//         "parentId": 1,
//         "children": []
//       },
//       {
//         "id": 3,
//         "name": "Child 1.2",
//         "parentId": 1,
//         "children": []
//       }
//     ]
//   },
//   {
//     "id": 4,
//     "name": "Root 2",
//     "parentId": null,
//     "children": [
//       {
//         "id": 5,
//         "name": "Child 2.1",
//         "parentId": 4,
//         "children": [
//           {
//             "id": 6,
//             "name": "SubChild 2.1.1",
//             "parentId": 5,
//             "children": []
//           }
//         ]
//       }
//     ]
//   }
// ]

function treeToList(tree) {
  const list = [];

  function traverse(node) {
    const { children, ...rest } = node;
    list.push(rest);

    if (children) {
      for (const child of children) {
        traverse(child);
      }
    }
  }

  for (const root of tree) {
    traverse(root);
  }

  return list;
}

const flatList = treeToList(tree);
console.log(flatList);
// [
//   { id: 1, name: 'Root 1', parentId: null },
//   { id: 2, name: 'Child 1.1', parentId: 1 },
//   { id: 3, name: 'Child 1.2', parentId: 1 },
//   { id: 4, name: 'Root 2', parentId: null },
//   { id: 5, name: 'Child 2.1', parentId: 4 },
//   { id: 6, name: 'SubChild 2.1.1', parentId: 5 }
// ]

Flat Nested Items with Category and Subcategory

This transformation turns deeply nested category data into a flat array where each item keeps the names of its parent subcategory and category.

The first version uses explicit loops. The second version uses flatMap() to express the same flattening directly.

const data = [
  {
    id: 1,
    name: "Category A",
    items: [
      {
        id: 2,
        name: "Subcategory A1",
        items: [
          { id: 3, name: "Item A1-1", value: 10 },
          { id: 4, name: "Item A1-2", value: 15 },
        ],
      },
      {
        id: 5,
        name: "Subcategory A2",
        items: [
          { id: 6, name: "Item A2-1", value: 20 },
          { id: 7, name: "Item A2-2", value: 25 },
        ],
      },
    ],
  },
  {
    id: 8,
    name: "Category B",
    items: [
      {
        id: 9,
        name: "Subcategory B1",
        items: [
          { id: 10, name: "Item B1-1", value: 30 },
          { id: 11, name: "Item B1-2", value: 35 },
        ],
      },
      {
        id: 12,
        name: "Subcategory B2",
        items: [
          { id: 13, name: "Item B2-1", value: 40 },
          { id: 14, name: "Item B2-2", value: 45 },
        ],
      },
    ],
  },
];

function transformItems(data) {
  const transformedItems = [];

  for (const category of data) {
    for (const subcategory of category.items) {
      for (const item of subcategory.items) {
        transformedItems.push({
          id: item.id,
          name: item.name,
          value: item.value,
          subcategory: subcategory.name,
          category: category.name,
        });
      }
    }
  }

  return transformedItems;
}

const itemsWithCategories = transformItems(data);

console.log(itemsWithCategories);

// [
//   {
//     id: 3,
//     name: "Item A1-1",
//     value: 10,
//     subcategory: "Subcategory A1",
//     category: "Category A",
//   },
//   {
//     id: 4,
//     name: "Item A1-2",
//     value: 15,
//     subcategory: "Subcategory A1",
//     category: "Category A",
//   },
//   {
//     id: 6,
//     name: "Item A2-1",
//     value: 20,
//     subcategory: "Subcategory A2",
//     category: "Category A",
//   },
//   {
//     id: 7,
//     name: "Item A2-2",
//     value: 25,
//     subcategory: "Subcategory A2",
//     category: "Category A",
//   },
//   {
//     id: 10,
//     name: "Item B1-1",
//     value: 30,
//     subcategory: "Subcategory B1",
//     category: "Category B",
//   },
//   {
//     id: 11,
//     name: "Item B1-2",
//     value: 35,
//     subcategory: "Subcategory B1",
//     category: "Category B",
//   },
//   {
//     id: 13,
//     name: "Item B2-1",
//     value: 40,
//     subcategory: "Subcategory B2",
//     category: "Category B",
//   },
//   {
//     id: 14,
//     name: "Item B2-2",
//     value: 45,
//     subcategory: "Subcategory B2",
//     category: "Category B",
//   },
// ];

function transformItems2(data) {
  return data.flatMap((category) =>
    category.items.flatMap((subcategory) =>
      subcategory.items.map((item) => ({
        id: item.id,
        name: item.name,
        value: item.value,
        subcategory: subcategory.name,
        category: category.name,
      })),
    ),
  );
}

const itemsWithCategories2 = transformItems2(data);
console.log(itemsWithCategories2);

// ...same result

On this page