Home > Blockchain >  Merge duplicates in array of objects and mutate some properties javascript
Merge duplicates in array of objects and mutate some properties javascript

Time:06-07

I have this complex scenario in which I need to find duplicates in an array of objects, and merge them mutating some property values.

The data comes from an excel file which has reservations for an hotel, each reservation has a reservationNumber which is unique, when the array contains 2 or more reservations with the same reservationNumber it means that it's the same reservation but with multiple rooms, so I need them to be merged, but the first item should remain as the roomNumber (which is the main room) and the rest of the duplicates should aggregate to extra_rooms (which is a simple array containing the remaining room numbers, excluding the main room); Also it needs to aggregate to extraRoomsAges property with the following structure:

extraRoomsAges: [
   {
     room: 34, // We get this from the roomNumber property
     // We get this from the mainRoomDistribution property
     adults: 0,
     infants: 0,
   },
],

So to get a wider view of the problem this is an example of the data I need to process:

const data = [
  {
    reservationNumber: 'RX-0032466',
    fullName: 'John Doe',
    firstName: 'John',
    lastName: 'Doe',
    documentType: 'dni',
    hotel: 'hpp',
    documentID: null,
    extra_rooms: [],
    extraRoomsAges: [],
    mainRoomDistribution: {
      adults: 2,
      infants: 0,
    },
    totalCharges: 1754.28,
    agencyCode: 5,
    roomNumber: '63',
  },
  {
    reservationNumber: 'RX-0032466',
    fullName: 'John Doe',
    firstName: 'John',
    lastName: 'Doe',
    documentType: 'dni',
    hotel: 'hpp',
    documentID: null,
    extra_rooms: [],
    extraRoomsAges: [],
    mainRoomDistribution: {
      adults: 3,
      infants: 1,
    },
    totalCharges: 1954.49,
    agencyCode: 5,
    roomNumber: '64',
  },
  {
    reservationNumber: 'RX-0032466',
    fullName: 'John Doe',
    firstName: 'John',
    lastName: 'Doe',
    documentType: 'dni',
    hotel: 'hpp',
    documentID: null,
    extra_rooms: [],
    extraRoomsAges: [],
    mainRoomDistribution: {
      adults: 1,
      infants: 0,
    },
    totalCharges: 850.30,
    agencyCode: 5,
    roomNumber: '65',
  },
  {
    reservationNumber: 'RX-0032494',
    fullName: 'Jane Foster',
    firstName: 'Jane',
    lastName: 'Foster',
    documentType: 'dni',
    hotel: 'hpp',
    documentID: '123456789',
    extra_rooms: [],
    extraRoomsAges: [],
    mainRoomDistribution: {
      adults: 4,
      infants: 0,
    },
    totalCharges: 1540,
    agencyCode: 13,
    roomNumber: '40',
  },
]

The result should look like this:

  [
    {
      reservationNumber: 'RX-0032466',
      fullName: 'John Doe',
      firstName: 'John',
      lastName: 'Doe',
      documentType: 'dni',
      hotel: 'hpp',
      documentID: null,
      extra_rooms: [64, 65], // aggregates the remaining occurrences from the roomNumber property
      extraRoomsAges: [
        {
          room: 64,
          adults: 3,
          infants: 1
        },
        {
          room: 65,
          adults: 1,
          infants: 0,
        }
      ],
      mainRoomDistribution: {
        adults: 2,
        infants: 0,
      },
      totalCharges: 4559.07, //the sum of all the occurrences
      agencyCode: 5,
      roomNumber: '63', //remains as the main room from the first occurrence
    },
    {
      reservationNumber: 'RX-0032494',
      fullName: 'Jane Foster',
      firstName: 'Jane',
      lastName: 'Foster',
      documentType: 'dni',
      hotel: 'hpp',
      documentID: null,
      extra_rooms: [],
      extraRoomsAges: [],
      mainRoomDistribution: {
        adults: 4,
        infants: 0,
      },
      totalCharges: 1540,
      agencyCode: 13,
      roomNumber: '40',
    },
  ]

So far I've tried with this function to merge duplicates:

export function mergeObjectsInUnique<T>(array: T[], property: any): T[] {
  const newArray = new Map();
  array.forEach((item: T) => {
    const propertyValue = item[property];
    newArray.has(propertyValue)
      ? newArray.set(propertyValue, { ...item, ...newArray.get(propertyValue) })
      : newArray.set(propertyValue, item);
  });

  return Array.from(newArray.values());
}

console.log(mergeObjectsInUnique(data, 'reservationNumber'))

As you can see this merges the duplicates correctly, but I just can't find a way to mutate the data properly as I need it.

Snippet with current code

const data = [{
    reservationNumber: 'RX-0032466',
    fullName: 'John Doe',
    firstName: 'John',
    lastName: 'Doe',
    documentType: 'dni',
    hotel: 'hpp',
    documentID: null,
    extra_rooms: [],
    extraRoomsAges: [],
    mainRoomDistribution: {
      adults: 2,
      infants: 0,
    },
    totalCharges: 1754.28,
    agencyCode: 5,
    roomNumber: '63',
  },
  {
    reservationNumber: 'RX-0032466',
    fullName: 'John Doe',
    firstName: 'John',
    lastName: 'Doe',
    documentType: 'dni',
    hotel: 'hpp',
    documentID: null,
    extra_rooms: [],
    extraRoomsAges: [],
    mainRoomDistribution: {
      adults: 3,
      infants: 1,
    },
    totalCharges: 1954.49,
    agencyCode: 5,
    roomNumber: '64',
  },
  {
    reservationNumber: 'RX-0032466',
    fullName: 'John Doe',
    firstName: 'John',
    lastName: 'Doe',
    documentType: 'dni',
    hotel: 'hpp',
    documentID: null,
    extra_rooms: [],
    extraRoomsAges: [],
    mainRoomDistribution: {
      adults: 1,
      infants: 0,
    },
    totalCharges: 850.30,
    agencyCode: 5,
    roomNumber: '65',
  },
  {
    reservationNumber: 'RX-0032494',
    fullName: 'Jane Foster',
    firstName: 'Jane',
    lastName: 'Foster',
    documentType: 'dni',
    hotel: 'hpp',
    documentID: '123456789',
    extra_rooms: [],
    extraRoomsAges: [],
    mainRoomDistribution: {
      adults: 4,
      infants: 0,
    },
    totalCharges: 1540,
    agencyCode: 13,
    roomNumber: '40',
  },
]

function mergeObjectsInUnique(array, property) {
  const newArray = new Map();
  array.forEach((item) => {
    const propertyValue = item[property];
    newArray.has(propertyValue) ?
      newArray.set(propertyValue, { ...item,
        ...newArray.get(propertyValue)
      }) :
      newArray.set(propertyValue, item);
  });

  return Array.from(newArray.values());
}

console.log(mergeObjectsInUnique(data, 'reservationNumber'))

Here is a JSFiddle to play around:

https://jsfiddle.net/ou036zwv/

I'd appreciate an explanation with the answer if possible.

Thanks in advance.

CodePudding user response:

I'd suggest using Array.reduce() here, grouping the reservations using the reservationNumber.

We'd create an object with properties for each reservation number, assigning each reservation to that property.

If a reservation already exists for the reservation number, we'll merge by appending the extra room numbers and extra room ages to the relevant arrays. We'd also add the totalCharges to the relevant property.

Once we've merged to this object, we'll use Object.values() to return to an array.

This approach is very efficient, the time taken will be of order O(N), since the acc[el.reservationNumber] lookups will be very fast.

const data = [ { reservationNumber: 'RX-0032466', fullName: 'John Doe', firstName: 'John', lastName: 'Doe', documentType: 'dni', hotel: 'hpp', documentID: null, extra_rooms: [], extraRoomsAges: [], mainRoomDistribution: { adults: 2, infants: 0, }, totalCharges: 1754.28, agencyCode: 5, roomNumber: '63', }, { reservationNumber: 'RX-0032466', fullName: 'John Doe', firstName: 'John', lastName: 'Doe', documentType: 'dni', hotel: 'hpp', documentID: null, extra_rooms: [], extraRoomsAges: [], mainRoomDistribution: { adults: 3, infants: 1, }, totalCharges: 1954.49, agencyCode: 5, roomNumber: '64', }, { reservationNumber: 'RX-0032466', fullName: 'John Doe', firstName: 'John', lastName: 'Doe', documentType: 'dni', hotel: 'hpp', documentID: null, extra_rooms: [], extraRoomsAges: [], mainRoomDistribution: { adults: 1, infants: 0, }, totalCharges: 850.30, agencyCode: 5, roomNumber: '65', }, { reservationNumber: 'RX-0032494', fullName: 'Jane Foster', firstName: 'Jane', lastName: 'Foster', documentType: 'dni', hotel: 'hpp', documentID: '123456789', extra_rooms: [], extraRoomsAges: [], mainRoomDistribution: { adults: 4, infants: 0, }, totalCharges: 1540, agencyCode: 13, roomNumber: '40', }, ]

function mapRoomData(roomData) {
    return Object.values(roomData.reduce((acc, el) => { 
        if (acc[el.reservationNumber]) {
            // Merge data with existing reservation. 
            acc[el.reservationNumber].extra_rooms.push( el.roomNumber);
            acc[el.reservationNumber].extraRoomsAges.push({ room:  el.roomNumber, ...el.mainRoomDistribution } );
            acc[el.reservationNumber].totalCharges  = el.totalCharges;
        } else {
            // No reservation exists, create one...
            acc[el.reservationNumber] = el
        }
        return acc;
    }, {}))
}

const result = mapRoomData(data);
console.log('Result:', result);
.as-console-wrapper { max-height: 100% !important; }

  • Related