Search code examples
javascriptarraysmergelodash

Lodash Merge Two Arrays and categorize it


i neeed to merge two arrays: Categories and Products. Each product has a category object. I need to organize by category, include the category object and keep the empty categories. GroupBy function include only one parameter.

const Categories= [   
  {id: 1, 'name': 'category1'}
  {id: 2, 'name': 'category2'},
  {id: 3, 'name': 'category3'},
  {id: 4, 'name': 'category4'},    
]
const Products= [   
  {id: 1, 'name': 'product1', category: {id: 1, name: 'category1'}},
  {id: 2, 'name': 'product2', category: {id: 1, name: 'category1'}},
  {id: 3, 'name': 'product3', category: {id: 2, name: 'category2'}},
  {id: 4, 'name': 'product4', category: {id: 2, name: 'category2'}},    
]

expected result

const result = [
  {
    category: {id: 1, name: 'category1'}, 
    products:[{id:1, name: 'produt1'}, {id: 2, name: 'produto1'} ]
  },
  {
    category: {id: 2, name: 'category2'}, 
    products:[{id:3, name: 'produt3'}, {id: 4, name: 'produto4'} ]
  },
  {
    category: {id: 3, name: 'category3'}, 
    products:[]
  },
 {
    category: {id: 4, name: 'category4'}, 
    products:[]
  },
]

attempts:

 for (i = 0; i < categoriesJson.length; i++) {
            categoriesJson[i] =   _.assign({}, categoriesJson[i], { products: [] })
            for (j = 0; j < productsJson.length; j++) {
                if(productsJson[j].categoryId.objectId === categoriesJson[i].objectId){
                    categoriesJson[i].products.push(productsJson[j])
                }
            }
        }

Solution

  • Concat the Categories (formatted by to a Product format) to the Products, group by the category.id, and then map each group - category is taken from the 1st item, while products are the the items in groups, without the category, and empty items are rejected:

    const Products = [{"id":1,"name":"product1","category":{"id":1,"name":"category1"}},{"id":2,"name":"product2","category":{"id":1,"name":"category1"}},{"id":3,"name":"product3","category":{"id":2,"name":"category2"}},{"id":4,"name":"product4","category":{"id":2,"name":"category2"}}]
    const Categories = [{"id":1,"name":"category1"},{"id":2,"name":"category2"},{"id":3,"name":"category3"},{"id":4,"name":"category4"}]
    
    const result = _(Products)
      .concat(Categories.map(category => ({ category })))
      .groupBy('category.id')
      .map(group => ({
        category: _.head(group).category,
        products: _(group)
          .map(o => _.omit(o, 'category'))
          .reject(_.isEmpty)
          .value()
      }))
      .value()
    
    console.log(result)
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

    And the same idea with lodash/fp. Wrap the _.flow() with the _.useWith() function, and preformat the Categories (2nd param) to fit the Categories. The rest is similar to the lodash chain.

    const { useWith, identity, flow, concat, groupBy, map, head, omit, reject, isEmpty } = _
    
    const formatProducts = flow(map(omit('category')), reject(isEmpty))
    
    const fn = useWith(flow(
      concat,
      groupBy('category.id'),
      map(group => ({
        category: head(group).category,
        products: formatProducts(group)
      }))
    ), [identity, map(category => ({ category }))])
    
    const Products = [{"id":1,"name":"product1","category":{"id":1,"name":"category1"}},{"id":2,"name":"product2","category":{"id":1,"name":"category1"}},{"id":3,"name":"product3","category":{"id":2,"name":"category2"}},{"id":4,"name":"product4","category":{"id":2,"name":"category2"}}]
    const Categories = [{"id":1,"name":"category1"},{"id":2,"name":"category2"},{"id":3,"name":"category3"},{"id":4,"name":"category4"}]
    
    const result = fn(Products, Categories)
    
    console.log(result)
    <script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>