Search code examples
javascriptarraystypescriptobjectarray-merge

How to create a new array of objects from two arrays based on object values?


I'm struggling with merging two arrays of objects (fetched from blockchain data) into a new array based on object values.

The goal is to get the latest interaction with a user.

A simplified but close representation of the data structure this problem is faced in:

interface MsgSlice {
    messageId: string;
    messageDataSlice: {
        senderId?: string;
        receiverId: string;
        timestamp: number;
    };
};

const latestReceivedMsgs: MsgSlice[] = [
    {
        messageId: "messageId1",
        messageDataSlice: {
            senderId: "userId1",
            receiverId: "ownerId", // <- always same in that array
            timestamp: 101,
        },
    },
    {
        messageId: "messageId3",
        messageDataSlice: {
            senderId: "userId2",
            receiverId: "ownerId",
            timestamp: 103,
        },
    },
    {
        messageId: "messageId5",
        messageDataSlice: {
            senderId: "userId3",
            receiverId: "ownerId",
            timestamp: 105,
        },
    },
];

const latestSentMsgs: MsgSlice[] = [
    {
        messageId: "messageId2",
        messageDataSlice: {
            // senderId: "ownerId",
            receiverId: "userId1",
            timestamp: 102,
        },
    },
    {
        messageId: "messageId4",
        messageDataSlice: {
            receiverId: "userId3",
            timestamp: 104,
        },
    },
];

The desired result should contain the latest messageId either 'sent to' or 'received by' the corresponding user. Something like this:


const latestInteraction = [
    {
        user: "userId1",
        messageId: "messageId2",
        timestamp: 102,
    },
    {
        user: "userId2",
        messageId: "messageId3",
        timestamp: 103,
    },
    {
        user: "userId3",
        messageId: "messageId5",
        timestamp: 105,
    },
]   

As a solution I thought of looping over the arrays and per iteration also looping over the other array to compare the senderId and receiverId values. If "senderId is == one of the looped receiverIds", it could be sent into an interaction array and then time sorted and filtered. Unfortunately, I couldn't figure out how to get it working. My thinking might be limited here, and there are likely more efficient ways to do it than in my solution concept.


Solution

  • You can use the hash grouping approach, the vanila JS solution

    Live Demo:

    const latestReceivedMsgs = [{messageId: "messageId1",messageDataSlice: {senderId: "userId1",receiverId: "ownerId", timestamp: 101,},},{messageId: "messageId3",messageDataSlice: {senderId: "userId2",receiverId: "ownerId",timestamp: 103,},},{messageId: "messageId5",messageDataSlice: {senderId: "userId3",receiverId: "ownerId",timestamp: 105,},},];
    const latestSentMsgs = [{messageId: "messageId2",messageDataSlice: {receiverId: "userId1",timestamp: 102,},},{messageId: "messageId4",messageDataSlice: {receiverId: "userId3",timestamp: 104,},},];
    
    const grouped = [...latestReceivedMsgs, ...latestSentMsgs]
      .reduce((acc, { messageId, messageDataSlice }) => {
        const { timestamp, senderId, receiverId } = messageDataSlice;
        const user = senderId ?? receiverId;
        const msgItem = { user, messageId, timestamp };
        if ((acc[user]?.timestamp ?? 0) < timestamp) acc[user] = msgItem;
        
        return acc;
      }, {});
    
    const result = Object.values(grouped);
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0 }

    UPDATE

    Or typescript variant:

    interface MsgSlice {
      messageId: string;
      messageDataSlice: {
        senderId?: string;
        receiverId?: string;
        timestamp: number;
      };
    };
    
    interface Interaction {
      user: string
      messageId: string
      timestamp: number
    };
    
    const latestReceivedMsgs: MsgSlice[] = [{messageId: "messageId1",messageDataSlice: {senderId: "userId1",receiverId: "ownerId", // <- always same in that array},},{messageId: "messageId3",messageDataSlice: {senderId: "userId2",receiverId: "ownerId",timestamp: 103,},},{messageId: "messageId5",messageDataSlice: {senderId: "userId3",receiverId: "ownerId",timestamp: 105,},},];
    const latestSentMsgs: MsgSlice[] = [{messageId: "messageId2",messageDataSlice: {receiverId: "userId1",timestamp: 102,},},{messageId: "messageId4",messageDataSlice: {receiverId: "userId3",timestamp: 104,},},];
    
    const grouped = ([...latestReceivedMsgs, ...latestSentMsgs] as MsgSlice[])
      .reduce((acc, { messageId, messageDataSlice }) => {
        const { timestamp, senderId, receiverId } = messageDataSlice;
        const user = senderId ?? receiverId ?? "unindefined";
        const msgItem = { user, messageId, timestamp };
        if ((acc[user]?.timestamp ?? 0) < timestamp) acc[user] = msgItem
        
        return acc;
      }, {} as { [key: Interaction['user']]: Interaction });
    
    const result: Interaction[] = Object.values(grouped);
    
    console.log(result);