1

In TypeScript, let's say that I have an array of Person objects.

And we have an array named myPersons:

[
 {'age': '25', 'name': 'mike'},
 {'age': '25', 'name': 'john'},
 {'age': '25', 'name': 'charles'},
 {'age': '30', 'name': 'melanie'},
 {'age': '30', 'name': 'cindy'}
]

I'd like to parse this array using Lodash or some not-so-messy way to get an array of objects where the Object has a key 'age' that maps to a string and a key 'names' that maps to an array of strings.

So the final resulting array would look like the following:

[
 {'age': '25', 'names': ['john', 'mike', 'charles']},
 {'age': '30', 'names': ['cindy', 'melanie']}
]

I can do the above using some messy for-loops but I'm new to frontend development and I have a good feeling it's not the best way. Any advice or guidance would be appreciated.

Jamm H
  • 31
  • 3
  • 1
    Possible duplicate of [What is the most efficient method to groupby on a JavaScript array of objects?](https://stackoverflow.com/questions/14446511/what-is-the-most-efficient-method-to-groupby-on-a-javascript-array-of-objects) – Heretic Monkey Jun 15 '18 at 20:45
  • If you want to use lodash for this task, you might do something like this: `Object.entries(_.groupBy(myPersons, a=>a.age)).map(a=> ({age:a[0], names: a[1].map(entry=>entry.name)}))` – Daniel Jun 15 '18 at 20:52

2 Answers2

0

You don't need lodash, or any other library, you can do this with TS. Loop over the array and assign to a new object, before returning.

grouped: { age: number, names: string[] }[] = [];

this.persons.forEach((person: Person) => {
  const groupIndex = this.grouped.findIndex((item: { age: number, names: string[] }) => {
    return item.age === person.age; });
    groupIndex !== -1 ? this.grouped[groupIndex].names.push(person.name)
    : this.grouped.push({age: person.age, names: [person.name]});
});

Then your array is available from this.grouped.

I would define an interface for the final objects to tidy it up and make it more succinct:

export interface NamesByAge {
    age?: number;
    names?: string[];
}

So the final code becomes:

grouped: NamesByAge[] = [];

this.persons.forEach((person: Person) => {
  const groupIndex = this.grouped.findIndex((item: NamesByAge) => {
    return item.age === person.age; });
    groupIndex !== -1 ? this.grouped[groupIndex].names.push(person.name)
    : this.grouped.push({age: person.age, names: [person.name]});
});

Granted, it's a bit wordy, but you can obviously shorten the variable names, and if you'd prefer an if rather than ternary, but if you don't want to include a library for this, it works :) Better still, as a function:

sortByKey(array: any[], sort: string, collectionKey: string, collectionName: string) {
    const grouped: any[] = [];
    array.forEach((p: any) => {
      const gId = grouped.findIndex((i: any) => {
        return i[sort] === p[sort];
      });
      if (gId !== -1) {
        grouped[gId][collectionName].push(p[collectionKey]);
      } else {
        const obj: {} = {};
        obj[sort] = p[sort];
        obj[collectionName] = [p[collectionKey]];
        grouped.push(obj);
      }
    });
    return grouped;


sorted = this.sortByKey(this.persons, 'age', 'name', 'names');
prettyfly
  • 2,962
  • 1
  • 25
  • 27
0

Lodash is not needed for this task. #NODASH :P

Here is vanilla JS version:

const groupByAge = (objectMap, { age, name }) => {
  if (objectMap[age]) objectMap[age].push(name)
  else objectMap[age] = [name]
  return objectMap
}

const pairsToArray = (acc, [age, names]) => {
  acc.push({ age, names })
  return acc
}

const fn = sourceArray => {
  const groupedObject = sourceArray.reduce(groupByAge, {})
  return Object
    .entries(groupedObject)
    .reduce(pairsToArray, [])
}

Here is TypeScript version with types declaration (es2017.object is required):

type GroupedObjectMap = { number: string[] }
type SourcePerson = { age: number, name: string }
type AgeGroup = { age: number, names: string[] }
type AgeGroupTuple = [number, string[]]

const groupByAge = (objectMap: {} | GroupedObjectMap, { age, name }: SourcePerson): {} | GroupedObjectMap => {
  if (objectMap[age]) objectMap[age].push(name)
  else objectMap[age] = [name]
  return objectMap
}

const pairsToArray = (acc: AgeGroup[], [age, names]: AgeGroupTuple): AgeGroup[] => {
  acc.push({ age, names })
  return acc
}

const fn = (sourceArray: SourcePerson[]): AgeGroup[] => {
  const groupedObject = sourceArray.reduce(groupByAge, {})
  return Object
    .entries(groupedObject)
    .reduce(pairsToArray, [])
}
amankkg
  • 4,503
  • 1
  • 19
  • 30