DiveDive
DiveDive

Reputation: 88

merge two array with common objects

I am working on a project that I fetch data from the server and every data must be like this format

const DATA = [
  {
    title: {title: 'Main dishes'},
    data: [
      {_id: 1, type: 'Pizza'},
      {_id: 2, type: 'Burger'},
      {_id: 3, type: 'Risotto'},
    ],
  },
  {
    title: {title: 'Sides'},
    data: [
      {_id: 4, type: 'French Fries'},
      {_id: 5, type: 'Onion Rings'},
      {_id: 6, type: 'Fried Shrimps'},
    ],
  },
];

but my problem is sometimes ill get the same title object within two or three fetches so I need to merge between them and hide duplicated data I am using this function


  merged = (data1, data2) => {
    let arr = data1.concat(data2);
    let titles = new Map(arr.map(({title}) => [title, new Map()]));

    arr.forEach(({title, data}) => {
      let map = titles.get(title);
      data.forEach(o => map.set(o._id, o));
    });

    return Array.from(titles.entries(), ([title, map]) => ({
      title,
      data: [...map.values()],
    }));
  };

but the problem is this function only work if the title in the object is a string

example :

const DATA = [
  {
    title: {title: 'Main dishes'},
    data: [
      {_id: 1, type: 'Pizza'},
      {_id: 2, type: 'Burger'},
      {_id: 3, type: 'Risotto'},
    ],
  },
  {
    title: {title: 'Sides'},
    data: [
      {_id: 4, type: 'French Fries'},
      {_id: 5, type: 'Onion Rings'},
      {_id: 6, type: 'Fried Shrimps'},
    ],
  },
];

const DATA2 = [
  {
    title: {title: 'Sides'},
    data: [{_id: 7, type: 'Lemon'}],
  },
  {
    title: {title: 'Drinks'},
    data: [
      {_id: 8, type: 'Water'},
      {_id: 9, type: 'Coke'},
      {_id: 10, type: 'Beer'},
    ],
  },
  {
    title: {title: 'Desserts'},
    data: [
      {_id: 11, type: 'Cheese Cake'},
      {_id: 12, type: 'Ice Cream'},
    ],
  },
];


must give me an array like this 

data = [
 {
    title: {title: 'Main dishes'},
    data: [
      {_id: 1, type: 'Pizza'},
      {_id: 2, type: 'Burger'},
      {_id: 3, type: 'Risotto'},
    ],
  },
  {
    title: {title: 'Sides'},
    data: [
      {_id: 4, type: 'French Fries'},
      {_id: 5, type: 'Onion Rings'},
      {_id: 6, type: 'Fried Shrimps'},
      {_id: 7, type: 'Lemon'},
    ],
  },
{
    title: {title: 'Drinks'},
    data: [
      {_id: 8, type: 'Water'},
      {_id: 9, type: 'Coke'},
      {_id: 10, type: 'Beer'},
    ],
  },
  {
    title: {title: 'Desserts'},
    data: [
      {_id: 11, type: 'Cheese Cake'},
      {_id: 12, type: 'Ice Cream'},
    ],
  },
]

so can anyone propose me a solution how to edit that function ? and thank you

Upvotes: 1

Views: 65

Answers (2)

Yevhen Horbunkov
Yevhen Horbunkov

Reputation: 15530

You may leverage Array.prototype.reduce() together with Map:

const   DATA=[{title:{title:"Main dishes"},data:[{_id:1,type:"Pizza"},{_id:2,type:"Burger"},{_id:3,type:"Risotto"}]},{title:{title:"Sides"},data:[{_id:4,type:"French Fries"},{_id:5,type:"Onion Rings"},{_id:6,type:"Fried Shrimps"}]}],
        DATA2=[{title:{title:"Sides"},data:[{_id:7,type:"Lemon"}]},{title:{title:"Drinks"},data:[{_id:8,type:"Water"},{_id:9,type:"Coke"},{_id:10,type:"Beer"}]},{title:{title:"Desserts"},data:[{_id:11,type:"Cheese Cake"},{_id:12,type:"Ice Cream"}]}]

const merged = [
  ...[...DATA, ...DATA2]
    .reduce((acc, item) => {
      const key = item.title.title
      const group = acc.get(key)
      if(group){
        const dataToInsert = item
          .data
          .filter(({_id: id}) => 
            !group
              .data
              .map(({_id}) => _id)
              .includes(id))
        group.data.push(...dataToInsert)
      } else {
        acc.set(key, item)
      }
      return acc
    }, new Map)
    .values()
]

console.log(merged)
.as-console-wrapper{min-height: 100%}

Upvotes: 1

Itay Ganor
Itay Ganor

Reputation: 4205

That's because each {title: 'foo-bar'} object is checked by reference when you do titles.get(title) and not by their value.

Either save the string titles in the titles map as keys, or use something like object-hash to turn objects into identifiers.

Using the string keys:

merged = (data1, data2) => {
  let arr = data1.concat(data2);
  let titles = new Map(arr.map(({title}) => [title.title, new Map()])); // notice the `title.title` instead of `title`

  arr.forEach(({title, data}) => {
    let map = titles.get(title.title); // `title.title` instead of `title`
    data.forEach(o => map.set(o._id, o));
  });
  return Array.from(titles.entries(), ([title, map]) => ({
    title,
    data: [...map.values()],
  }));
};

and using object-hash:

import hash from 'object-hash';

merged = (data1, data2) => {
  let arr = data1.concat(data2);
  let titles = new Map(arr.map(({title}) => [hash(title), new Map()])); 

  arr.forEach(({title, data}) => {
    let map = titles.get(hash(title));
    data.forEach(o => map.set(o._id, o));
  });
  return Array.from(titles.entries(), ([title, map]) => ({
    title,
    data: [...map.values()],
  }));
};

Note that hashing the objects might be overkill in this case. I think the string solution is enough.

Upvotes: 2

Related Questions