Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group into nested array of objects in Javascript

I have an array of categories:

[
  { categoryId: '01', categoryName: 'Byggmaterial' },
  { categoryId: '010', categoryName: 'Bindemedel och bruk' },
  { categoryId: '01001', categoryName: 'Cement' },
  { categoryId: '01002', categoryName: 'Bruksbindemedel' },
  { categoryId: '01003', categoryName: 'Kalkvaror' },
  { categoryId: '011', categoryName: 'Byggnadsblock och ballast' },
  { categoryId: '01101', categoryName: 'Betongblock' },
  { categoryId: '01102', categoryName: 'Tegel' },
  { categoryId: '02', categoryName: 'Byggmaterial' },
  { categoryId: '020', categoryName: 'Bindemedel och bruk' },
  { categoryId: '02001', categoryName: 'Cement' },
  { categoryId: '02002', categoryName: 'Bruksbindemedel' }
......
]

And an array of products:

[
  {
    productNumber: '01405',
    productName: 'SERVALAC AQUA BLANK - Utg�tt'
  },
  {
    productNumber: '01405',
    productName: 'SERVALAC AQUA HALVBLANK - Utg�tt'
  },
  {
    productNumber: '03405',
    productName: 'SERVALAC AQUA HALVBLANK - Utg�tt'
  },
  { productNumber: '03404', productName: 'SCOTTE GT-20 - UTG�TT' },
  { productNumber: '03404', productName: 'SCOTTE 7 - UTG�TT' },
  { productNumber: '03404', productName: 'SCOTTE 7 - UTG�TT' },
  { productNumber: '03404', productName: 'SCOTTE 7 - UTG�TT' },
  { productNumber: '03404', productName: 'SCOTTE 5 - UTG�TT' },
  { productNumber: '03404', productName: 'SCOTTE 20 - UTG�TT' },
......
]

I want to group each product in the corresponding category. The categories should be also grouped like this: main category (all the categories with 2 digits), second category (with 3 digits) and the next one (with 5 digits). Each product should be added to the corresponding main category, second and third category.

The result should be like this:

{
    "id":"01",
    "categoryName":"Byggmaterial",
    "items":[
        {
            "id":"010",
            "categoryName":"Bindemedel och bruk",
            "items": [
                {
                   "id":"01001",
                    "categoryName":"Cement",
                    "products": [
                          {
                             "productNumber":"01001",
                             "productName":"Tunnfog och Tunnputsbruk A"
                          },
                          {
                             "productNumber":"01001",
                             "productName":"Tunnfog"
                          },
                          .......
                    ]       
        }
                    ]       
        },

I'm stuck so I need help. I've tried with "for loop" inside "for loop", but I only succeeded to group all the products in main category.

{"id":"01","categoryName":"Byggmaterial","items":[{"productNumber":"01001","productName":"Tunnfog och Tunnputsbruk A"},{"productNumber":"01001","productName":"Lagningsmassa fin"},{"productNumber":"01399","productName":"Golvfoam Premium MFR"}, {"productNumber":"01199","productName":"THERMOMUR 350 STD SLUTET"}, {"productNumber":"01701","productName":"Adva Flow 484"},{"productNumber":"01706","productName":"Pieri Decobio C-23"}

Is there any other solution then "for loop"? I think that I cannot get the wanted result with only for loop. Thank you in advance!

like image 308
Heidi Avatar asked Sep 16 '25 21:09

Heidi


2 Answers

You can use two mutually recursive helper functions: one that selects subcategories for the given category:

function items(cid, level) {
    return categories
        .filter(c => c.categoryId.startsWith(cid) && c.categoryId.length === level)
        .map(process)
}

and one that processes a category:

function process(cat) {
    let cid = cat?.categoryId || ''

    switch (cid.length) {
        case 0:
            return {...cat, items: items(cid, 2)}
        case 2:
            return {...cat, items: items(cid, 3)}
        case 3:
            return {...cat, items: items(cid, 5)}
        case 5:
            return {
                ...cat,
                products: products.filter(p => p.productNumber.startsWith(cid))
            }
    }
}

Finally, just invoke process() with no arguments.

like image 109
gog Avatar answered Sep 19 '25 11:09

gog


You could build the catalog by preserving all categoryId which points to items and later push the products to the category.

const
    categories = [{ categoryId: '01', categoryName: 'Byggmaterial' }, { categoryId: '010', categoryName: 'Bindemedel och bruk' }, { categoryId: '01001', categoryName: 'Cement' }, { categoryId: '01002', categoryName: 'Bruksbindemedel' }, { categoryId: '01003', categoryName: 'Kalkvaror' }, { categoryId: '011', categoryName: 'Byggnadsblock och ballast' }, { categoryId: '01101', categoryName: 'Betongblock' }, { categoryId: '01102', categoryName: 'Tegel' }, { categoryId: '02', categoryName: 'Byggmaterial' }, { categoryId: '020', categoryName: 'Bindemedel och bruk' }, { categoryId: '02001', categoryName: 'Cement' }, { categoryId: '02002', categoryName: 'Bruksbindemedel' }],
    products = [{ productNumber: "01001", productName: "Tunnfog och Tunnputsbruk A" }, { productNumber: "01001", productName: "Tunnfog" }],
    catalog = categories.reduce((r, { categoryId, categoryName }) => {
        let i = categoryId.length;
        do {
            const key = categoryId.slice(0, i);
            if (key in r) {
                (r[key].items ??= []).push(r[categoryId] = { categoryId, categoryName });
                return r;
            }
        } while (--i)
        r.items.push(r[categoryId] = { categoryId, categoryName });
        return r;
    }, { items: [] });

products.forEach(o => (catalog[o.productNumber].product ??= []).push(o));

console.log(catalog.items);
.as-console-wrapper { max-height: 100% !important; top: 0; }
like image 27
Nina Scholz Avatar answered Sep 19 '25 10:09

Nina Scholz