'Javascript - Remove duplicate ID from array of objects

Take the following two arrays:

const array1 = [
  {
    props: {
      type : 'text',
      id : 'item1',
      name : 'item1',
      value : '@item1@',
    },
  },
  {
    props: {
      type: 'hidden',
      id: 'item2',
      name: 'item2',
      value: '@item2@',
    },
  }
];

const array2 = [
  {
    props: {
      type: 'hidden',
      id: 'item1',
      name: 'item1',
      value: '@item1@',
    },
  }
];

What I'm trying to do is concatenate them into a single array, and remove any duplicates based on the id property. However the caveat here is that the object that does NOT have a type of hidden must have presidence.

So I should basically be left with the contents of array1, as the duplicate item from array2 has the type value of hidden, like so:

// Result
[
  {
    props: {
      type : 'text', // Note the type here is "text"
      id : 'item1',
      name : 'item1',
      value : '@item1@',
    },
  },
  {
    props: {
      type: 'hidden',
      id: 'item2',
      name: 'item2',
      value: '@item2@',
    },
  }
];

I can easily concatenate them using:

const array = array1.concat(array2);

My idea was to then use a Filter but I'm having a bit of a brain melt. Here is what I have come up with so far:

const concat = (array1, array2) => {
  const array = array1.concat(array2);
  const ids = [];

  // Create array of ID's
  for (const i in array1) {
    ids.push(array1[i].props.id);
  }

  return array.filter((obj) => {
    if (obj.props.type !== 'hidden' && ids.includes(obj.props.id)) {
      return true;
    }

    return false;
  });
};

Would using a Reduce be a better approach here?

Here is a JSFiddle of what I have so far: https://jsfiddle.net/64uprbhn/



Solution 1:[1]

You can

  1. Create a Map of id => object from the first array
  2. Go over the second array and either
    • add the item if not already in the map
    • check if the current item is not type: "hidden" and overwrite the other one
    • otherwise discard the item
  3. Create a new array from the Map

const array1 = [
  {
    props: {
      type : 'text',
      id : 'item1',
      name : 'item1',
      value : '@item1@',
    },
  },
  {
    props: {
      type: 'hidden',
      id: 'item2',
      name: 'item2',
      value: '@item2@',
    },
  },
  {
    props: {
      type: 'hidden',
      id: 'item3',
      name: 'item3',
      value: '@item3@',
    },
  }
];

const array2 = [
  {
    props: {
      type: 'hidden',
      id: 'item1',
      name: 'item1',
      value: '@item1@',
    },
  },
  {
    props: {
      type: 'text',
      id: 'item3',
      name: 'item3',
      value: '@item3@',
    },
  }
];

//1. create a map from id => object pairs
const map = new Map(array1.map(obj => [obj.props.id, obj]));

//2. go over the second array
array2.forEach(obj => {
  if (!map.has(obj.props.id) //not a duplicate
    || obj.props.type !== "hidden") { //is not hidden
    //insert
    map.set(obj.props.id, obj);
  }
});

//3. convert back into array
const merged = [...map.values()];

console.log(merged);

For the record, you can basically do the same (or pretty similar) thing using .filter but you'll have to do a O(n) lookup for each item. A Map ensures much faster lookups.

Solution 2:[2]

const array1 = [
  {
    props: {
      type : 'text',
      id : 'item1',
      name : 'item1',
      value : '@item1@',
    },
  },
  {
    props: {
      type: 'hidden',
      id: 'item2',
      name: 'item2',
      value: '@item2@',
    },
  }
];

const array2 = [
  {
    props: {
      type: 'hidden',
      id: 'item1',
      name: 'item1',
      value: '@item1@',
    },
  }
];

function getUnique(arr, comp) {

  const unique = arr
    .map(e => e[comp])

    // store the keys of the unique objects
    .map((e, i, final) => final.indexOf(e) === i && i)

    // eliminate the dead keys & store unique objects
    .filter(e => arr[e]).map(e => arr[e]);

  return unique;
}

const arr = array1.concat(array2);

console.log(getUnique(arr, 'id'));

Solution 3:[3]

Here's what worked for me

const someItems = [{ id: 1 }, { id: 2 }, { id: 1 }]

function getUniqueItems(items) {
  const uniqueIds = new Set(items.map((item) => item.id))

  const itemsWithUniqueIds = [...uniqueIds].map(id => items.find(item => item.id === id)).filter(Boolean)

  return itemsWithUniqueIds
}

console.log(getUniqueItems(someItems))

If you are using TypeScript, TS will complain about the .filter(Boolean). In that case just replace Boolean with (item: any | undefined): item is any => Boolean(item). Of course, you can then go ahead and also replace any with whichever type makes sense in your case.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 georg
Solution 2 VLAZ
Solution 3 maninak