1

I have an array of names for an account. Each person can have more than one type. type is an enum..which should replace the values to their definitions. Want to group by accountNumber, firstName, lastName and not type.

export interface Name {
    accountNumber: string;    
    firstName: string|null;
    lastName: string|null;
     type: AccountType;
}
export enum AccountType{
    primary = 0,
    secondary = 1
    
}
const names =[
   {
      "accountNumber":"0000001420",
      "firstName":"KELLY",
      "lastName":"WHITE",
      "type":0
   },
   {
      "accountNumber":"0000001420",
      "firstName":"RAY",
      "lastName":"WHITE",
      "type":0
   },
   {
      "accountNumber":"0000001420",
      "firstName":"KELLY",
      "lastName":"WHITE",
      "type":1
   }
]

I want an output array with the following format:

[{
  firstName:"KELLY",
  lastName:"WHITE",
  accountNumber:"0000001420",
  types: [primary, secondary]
}, {
  firstName: "RAY",
  lastName: "WHITE",
  accountNumber: "0000001420",
  types: [primary]
}]

I tried using Reduce...

const merged2 = names.reduce((r, { accountNumber, firstName, lastName,type }) => {
  const key = `${accountNumber}-${firstName}-${lastName}`;
  
  r[key] = r[key] || { accountNumber, firstName, lastName, types: [] };
  r[key]["types"].push(type)
  return r;
}, {})

I get error at r[key]

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'.

How to fix the error or is there any other way to get the result instead of Reduce function?

Here is the link To TS Playground: https://www.typescriptlang.org/play?ssl=20&ssc=2&pln=1&pc=1#code/MYewdgzgLgBGCGBbAphGBeA2gKBng3rnsQEQqIBGyATgHICulNJAXCQAydcCMALAEzsSAGiLEYJAGYBLatFpJkrEgGkAogBkNATRFjSAG3jzFygOoAJAJIAVNXvF4SUAJ4AHJS3ZiAvqOKEjhLkVHSMocpcPAJC-o5SsiYoygBKAIK6ceIkRkmeJJa29lmkrh6s3sR+YoHxITQMTNSRUex8gg7xMnJQCsls6lqZ+k65vaZshXad2WWe3L7YALrY2NJgUDSS8MDIMH3IhNg+q6CQsCjUAObIACYYcIoQAHTUd-S7ABSf1MIw+DB6mEmn9unk-mMDsJnjC3tAYD4AJQYAB8-yIZ3hAGtkC4HgADAAk+CBjVCPgAtMSweMUJTiZDFD58QBuPBEaiYHEuJYPTnc3kAH0F-0ByCaZJooMStOQEOMsr+cwgLBgmF5PhZHK5uKWmGc7lQJCWzzc9AgAAsfqgoIiOcgoPRqGAYNQtX5-kjTuB4Zcbrd+A8ECgXm9bh9kN9fqLSeEpTAaVCYIyUMI5gjkeg0bVMbBuQTibGmvT8ImmVT8CnkMy2TBtQK+TqeTBhTHxaFJdGy6nkwqk8rVeqEVq8PzdfrlcbTearXM7aOHU6XW7jn98F7sLmYFBpCgbPAKAZkNwHgB5CgAK2QwCgzwAbvADPRUJ8-Xd+HbMSAj88DCArp8O57geR7cIiQA

Sugar Bowl
  • 1,722
  • 1
  • 19
  • 24

5 Answers5

2

First issue is that you need to specify type for the reduce accumulator (r in your code).

Second issue is the last line: }, {}) which should have been }, []) so that you init your accumulator with an empty array.

For converting 0 -> primary and 1 -> secondary we can use AccountType[AccountType.primary] and AccountType[AccountType.secondary]. Problem is that the returned value will be of type string. For more info on enum conversions, please have a look at e.g. this answer: https://stackoverflow.com/a/73325300/14072498

A straight-forward solution can be to use reduce and a separate interface for the result array like @boreddad420 do.

export interface AccumulatedName {
    accountNumber: string;
    firstName: string | null;
    lastName: string | null;
    types: string[];
}

const reducedNames = names.reduce(
    (accumulator: AccumulatedName[], currentName) => {
        const findIndex = accumulator.findIndex(it =>
            it.accountNumber === currentName.accountNumber
            && it.firstName === currentName.firstName
            && it.lastName === currentName.lastName
        )

        if (findIndex < 0) {
            accumulator.push({
                accountNumber: currentName.accountNumber,
                firstName: currentName.firstName,
                lastName: currentName.lastName,
                types: [AccountType[currentName.type]]
            })
        } else {
            accumulator[findIndex].types.push(AccountType[currentName.type])
        }
        return accumulator
    }, [])
Roar S.
  • 8,103
  • 1
  • 15
  • 37
1

You could try using a map, using the account numbers as the keys, assuming that they are all unique.

export interface Name {
    accountNumber: string;    
    firstName: string|null;
    lastName: string|null;
    type: AccountType;
}

export interface NameEntry {
    accountNumber: string;    
    firstName: string|null;
    lastName: string|null;
    types: AccountType[];
}

export enum AccountType{
    primary = 0,
    secondary = 1
}

// omitted names variable for length

const nameMap = new Map<string, NameEntry>();

names.forEach((name) => {
    if (nameMap.has(name.accountNumber)) {
        nameMap.get(name.accountNumber).types.push(name.type);
    } else {
        nameMap.set(name.accountNumber, {
            accountNumber: name.accountNumber,
            firstName: name.firstName,
            lastName: name.lastName,
            types: [name.type],
        });
    }
})

you can then iterate over the map like so:

// the value will be of type NameEntry
nameMap.forEach((value) => {
    console.log(value);
})

and get an entry for a given account number:

let accountNum = "0000001420";

let entry = nameMap.get(accountNum);

console.log(entry); // {
                    //       "accountNumber":"0000001420",
                    //       "firstName":"KELLY",
                    //       "lastName":"WHITE",
                    //       "types":[0,1]
                    // }
boreddad420
  • 187
  • 1
  • 7
  • You may simplify your code by using `names.reduce((accumulator: NameEntry[], currentName) => {...` – Roar S. Apr 20 '23 at 23:09
  • @RoarS. the code may be simpler, but working with the data in the array would not be as simple as working with it from the map. I guess it depends on the project, but I believe a map would be great in this scenario – boreddad420 Apr 20 '23 at 23:25
  • 1
    If someone is looking to work with map, then this could be a solution too. Thanks for your reply. It works as I had asked for. – Sugar Bowl Apr 24 '23 at 15:01
1

I just tried this on your TS Playground, and seems to be working fine:

const names =[
   {
      "accountNumber":"0000001420",
      "firstName":"KELLY",
      "lastName":"WHITE",
      "type":0
   },
   {
      "accountNumber":"0000001420",
      "firstName":"RAY",
      "lastName":"WHITE",
      "type":0
   },
   {
      "accountNumber":"0000001420",
      "firstName":"KELLY",
      "lastName":"WHITE",
      "type":1
   }
]

enum AccountType {
  primary = 0,
  secondary = 1,
}

interface Name {
  accountNumber: string;
  firstName: string | null;
  lastName: string | null;
  type: AccountType;
}

interface NameEntry {
  accountNumber: string;
  firstName: string | null;
  lastName: string | null;
  types: AccountType[];
}

const nameEntries = Object.values(
  names.reduce((acc: Record<string, NameEntry>, curr: Name) => {
    const key = `${curr.accountNumber}-${curr.firstName}-${curr.lastName}`;
    if (acc[key]) {
      acc[key].types.push(curr.type);
    } else {
      acc[key] = { ...curr, types: [curr.type] };
    }
    return acc;
  }, {})
);

console.log(nameEntries)
maxpsz
  • 431
  • 3
  • 9
0

You just need to explicity declare a type for r, even declaring it as any would work

const merged = names.reduce((r: any, { memberNumber, firstName, lastName,...rest }) => {

or you can declare a type for the map

  export interface GroupedName {
    accountNumber: string;    
    firstName: string|null;
    lastName: string|null;
    types: AccountType[];
}
interface GroupedNames {
    [key: string]: GroupedName;}

const merged = names.reduce((r: GroupedNames, { memberNumber, firstName, lastName,...rest }) => {

to get the same result you can use a simple for loop

const groupedNames: GroupedNames = {};
for(let i =0; i< names.length; i++) {
  const {accountNumber, firstName, lastName, type} = names[i];
  const key = `${accountNumber}-${firstName}-${lastName}`;
  if(!groupedNames[key]) {
     accountNumber, firstName, lastName, types:[]
  };

  groupedNames[key].types.push(type):

}
Maha BENJEBARA
  • 232
  • 4
  • 12
0

lodash groupby is easy to use. Then just a small map over the values, and you're good to go. Easy to read and reason about, unlike reduce which takes some getting used to. https://lodash.com/docs/4.17.15#groupBy

const res = groupBy(names, (name) => name.accountNumber+name.firstName+name.lastName)

You will get an object like this:

const res = {
    "0000001420KELLYWHITE": [
        {
            "accountNumber": "0000001420",
            "firstName": "KELLY",
            "lastName": "WHITE",
            "type": 0
        },
        {
            "accountNumber": "0000001420",
            "firstName": "KELLY",
            "lastName": "WHITE",
            "type": 1
        }
    ],
    "0000001420RAYWHITE": [
        {
            "accountNumber": "0000001420",
            "firstName": "RAY",
            "lastName": "WHITE",
            "type": 0
        }
    ]
}

If you just want the values you can use Object.values(res).

Get the type of each value and add together with the other values.

const answer = Object.values(res).map(array => {
    const types = array.map(obj => obj.type)
    const {type, ...returnObj} = array[0]
    return {...returnObj, types}
})

Final result is:

[
    {
        "accountNumber": "0000001420",
        "firstName": "KELLY",
        "lastName": "WHITE",
        "types": [
            0,
            1
        ]
    },
    {
        "accountNumber": "0000001420",
        "firstName": "RAY",
        "lastName": "WHITE",
        "types": [
            0
        ]
    }
]
Emanuel Lindström
  • 1,607
  • 16
  • 25