简体   繁体   中英

Merging two arrays based on Property

I have two arrays: one contains categories and the second contains items.

I would like to merge the all items items into their proper category array using lodash. However, I do have a question. Since this can be done with a double foreach loop, is there any reason to use lodash?

Array 1 (categories):

"categories": [
    {
        "id": 1,
        "name": "cat_1",
        "items": []
    },
    {
        "id": 11,
        "name": "cat_2",
        "items": []
    },
    {
        "id": 61,
        "name": "cat_3",
        "items": []
    }
]

Array 2 (contains items):

[ 
    { 
        id: 15,
        name: 'some_item',
        category_id: 1,
        category_name: 'cat_1',
    },
    { 
        id: 112,
        name: 'some_item',
        category_id: 11,
        category_name: 'cat_2',
    },
    { 
        id: 235,
        name: 'some_item',
        category_id: 11,
        category_name: 'cat_2',
    },

]

The desired object would look:

"categories": 
    [
        {
            "id": 1,
            "name": "cat_1",
            "items": [
                { 
                    id: 15,
                    name: 'some_item',
                    category_id: 1,
                    category_name: 'cat_1',
                }
            ]
        },
        {
            "id": 11,
            "name": "cat_2",
            "items": [
                { 
                    id: 112,
                    name: 'some_item',
                    category_id: 11,
                    category_name: 'cat_2',
                },
                { 
                    id: 235,
                    name: 'some_item',
                    category_id: 11,
                    category_name: 'cat_2',
                }
            ]
        },
        {
            "id": 61,
            "name": "cat_3",
            "items": []
        }
    ]

This is how you'd do it with plain ES5:

 const categories = [{id:1,name:"cat_1",items:[]},{id:11,name:"cat_2",items:[]},{id:61,name:"cat_3",items:[]}] const items = [{id:15,name:"some_item",category_id:1,category_name:"cat_1"},{id:112,name:"some_item",category_id:11,category_name:"cat_2"},{id:235,name:"some_item",category_id:11,category_name:"cat_2"}]; const result = categories.map(c => ({ ...c, items: items.filter(i => i.category_id === c.id) })); console.log(result); 

Javascript is a very special language because of it's history. It started as a very humble language with little features, and although it has evolved, not all of it's features are supported by all browsers.

This caused the apparition of libraries like lodash, underscore, inmutable.js, and jquery, which came to fill the gap in Javascript's standard library.

As you see though, now JS has very convenient abstractions like each , map , find , filter and reduce , which are universally understood concepts across languages. This makes code much more understandable than when you write low-level, procedural code using for loops.

As a color note, I've started programming with ruby, which is known for having a standard library rich in collection abstractions . As a result, I did not notice there where for loops in the language until very recently, even when they are necessary to implement the higher level abstractions.

I have never used lodash, but you're right, this could be quite easily done with a foreach loop. Not even a double foreach loop, a single one with a '.find' or '.filter'

(a) loop through each item and use array.find to find the appropriate category, and input the item into the category

(b) loop through each category and use array.filter to find the appropriate items, and input those items into each category.

Here's one option laid out:

(()=>{
  const categories = [{
      "id": 1,
      "name": "cat_1",
      "items": []
    },
    {
      "id": 11,
      "name": "cat_2",
      "items": []
    },
    {
      "id": 61,
      "name": "cat_3",
      "items": []
    }]

  const items = [{ 
      id: 15,
      name: 'some_item',
      category_id: 1,
      category_name: 'cat_1',
    },
    { 
      id: 112,
      name: 'some_item',
      category_id: 11,
      category_name: 'cat_2',
    },
    { 
      id: 235,
      name: 'some_item',
      category_id: 11,
      category_name: 'cat_2',
    }]

  categories.forEach((cat) => {
    cat.items = items.filter((item) => item.category_id === cat.id);
  })
})()

You can group the items by the category id using lodash _.keyBy() , and then loop the categories with Array#map , and create new categories objects, with the items takes from itemsByCategory :

 const categories = [{"id":1,"name":"cat_1","items":[]},{"id":11,"name":"cat_2","items":[]},{"id":61,"name":"cat_3","items":[]}]; const items = [{"id":15,"name":"some_item","category_id":1,"category_name":"cat_1"},{"id":112,"name":"some_item","category_id":11,"category_name":"cat_2"},{"id":235,"name":"some_item","category_id":11,"category_name":"cat_2"}]; const itemsByCategory = _.groupBy(items, 'category_id'); const categoriesWithItems = categories.map(({ id, name, items }) => ({ id, name, items: items.concat(itemsByCategory[id]) })); console.log(categoriesWithItems); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script> 

As you can see, the only lodash method I've used is _.keyBy() , which we can replace with Array#reduce :

 const categories = [{"id":1,"name":"cat_1","items":[]},{"id":11,"name":"cat_2","items":[]},{"id":61,"name":"cat_3","items":[]}]; const items = [{"id":15,"name":"some_item","category_id":1,"category_name":"cat_1"},{"id":112,"name":"some_item","category_id":11,"category_name":"cat_2"},{"id":235,"name":"some_item","category_id":11,"category_name":"cat_2"}]; const itemsByCategory = items.reduce((r, item) => { r[item.category_id] = item; return r; }, {}); const categoriesWithItems = categories.map(({ id, name, items }) => ({ id, name, items: items.concat(itemsByCategory[id]) })); console.log(categoriesWithItems); 

So, using lodash is a matter of convenience mostly. Since lodash is written in JS, you can create anything that lodash do by using JS (and ES6 makes life even easier), and it saves some bundle size. However, lodash does may save lots of handwritten purely tested code, which reinvents the wheel. I suggest using lodash-es instead of lodash, since the es version is modular, and you can import just what you need. This balances the ease of use, with the bundle size.

You could take a Map for the categories and then iterate items for pushing to the corresponding category.

 var object = { categories: [{ id: 1, name: "cat_1", items: [] }, { id: 11, name: "cat_2", items: [] }, { id: 61, name: "cat_3", items: [] }] }, items = [{ id: 15, name: 'some_item', category_id: 1, category_name: 'cat_1', }, { id: 112, name: 'some_item', category_id: 11, category_name: 'cat_2', }, { id: 235, name: 'some_item', category_id: 11, category_name: 'cat_2', }], map = new Map(object.categories.map(category => [category.id, category.items])); items.forEach(item => map.get(item.category_id).push(item)); console.log(object); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM