Power of Eloquence

Tips and Tricks for Working with Arrays of Objects in Javascript and Typescript

| Comments

Generated AI image by Microsoft Bing Image Creator

Introduction:

Since my new types of interesting engineering work have been mostly tied with backend and data engineering as of late, I’ve been earger to do a check in on what’s been happening lately in the web browser space and how much the world of web and front end development has changed since stepping away little over 18 months at the time of writing, particularly when working and manipulating with arrays of objects.

Sure enough, modern JavaScript and TypeScript, powered by the latest ECMAScript features, are loaded with powerful tools for working with arrays of objects. They enable concise, readable, and efficient code, making it simpler to manage, search, filter, and transform data structures. In this blog post, I’ll cover actionable tips and tricks to level up your expertise in handling arrays of objects, starting with some refresher basics up to something more advanced takes.

1. Iterate Like a Pro Using .map, .forEach, and Optional Chaining

When working with arrays of objects, you’ll often need to transform the elements or interact with their properties. The .map method is great for returning a new array, while .forEach is ideal for side effects like logging or modifying values.

Example:

// Array of objects
const users = [
  { id: 1, name: "Alice", age: 28 },
  { id: 2, name: "Bob", age: 34 },
  { id: 3, name: "Charlie", age: 25 },
];

// Transform with .map
const userNames = users.map((user) => user.name); // ['Alice', 'Bob', 'Charlie']

// Optional chaining to avoid undefined errors
const userAges = users.map((user) => user.age?.toString() || "Age unavailable"); // ['28', '34', '25']

console.log(userNames);
console.log(userAges);

2. Perform Efficient Search with .find and .some

Finding a specific object or verifying the presence of a condition is effortless through .find and .some.

Example:

// Find specific user by ID
const user = users.find((user) => user.id === 2);
console.log(user); // { id: 2, name: 'Bob', age: 34 }

// Check if any user meets a condition
const hasOlderUser = users.some((user) => user.age > 30);
console.log(hasOlderUser); // true

3. Filter Arrays Easily with .filter

When you need to work with specific subsets, .filter is your best friend.

Example:

// Filter users by age condition
const youngUsers = users.filter((user) => user.age < 30);
console.log(youngUsers); // [{ id: 1, name: 'Alice', age: 28 }, { id: 3, name: 'Charlie', age: 25 }]

4. Sort Objects with .sort

Sorting an array based on specific object properties is straightforward with .sort.

Example:

const sortedByAge = users.sort((a, b) => a.age - b.age);
console.log(sortedByAge);
// [{ id: 3, name: 'Charlie', age: 25 }, { id: 1, name: 'Alice', age: 28 }, { id: 2, name: 'Bob', age: 34 }]

Use .slice() to prevent mutating the original array:

const sortedImmutable = users.slice().sort((a, b) => a.age - b.age);

5. Group Objects with .reduce

.reduce isn’t just for summing numbers—it can group objects based on properties.

Example:

const usersByAge = users.reduce((acc, user) => {
  const ageGroup = user.age < 30 ? "under30" : "over30";
  (acc[ageGroup] ||= []).push(user);
  return acc;
}, {} as Record<string, typeof users>);

console.log(usersByAge);
// {
//   under30: [
//     { id: 1, name: 'Alice', age: 28 },
//     { id: 3, name: 'Charlie', age: 25 }
//   ],
//   over30: [
//     { id: 2, name: 'Bob', age: 34 }
//   ]
// }

6. Remove Duplicate Objects Using Sets and .map

Deduplicating arrays of objects based on specific properties can be achieved using Sets and .map.

Example:

const uniqueUsers = Array.from(
  new Map(users.map((user) => [user.id, user])).values()
);
console.log(uniqueUsers);

// Deduplicates users based on their `id` property

7. Combine Multiple Arrays with Spread Syntax

The spread operator (...) is an easy way to merge arrays of objects.

Example:

const moreUsers = [{ id: 4, name: "David", age: 32 }];
const allUsers = [...users, ...moreUsers];
console.log(allUsers);

// Merged array of objects

8. TypeScript Bonus Tips: Leverage Interfaces and Generics

TypeScript allows you to ensure type safety when working with arrays of objects. Use interfaces or types for clarity.

Example:

interface User {
  id: number;
  name: string;
  age: number;
}

const typedUsers: User[] = [
  { id: 1, name: "Alice", age: 28 },
  { id: 2, name: "Bob", age: 34 },
  { id: 3, name: "Charlie", age: 25 },
];

// Access with proper autocomplete and safety
typedUsers.map((user) => user.name);

Generics in functions can add flexibility:

function getById<T extends { id: number }>(
  array: T[],
  id: number
): T | undefined {
  return array.find((item) => item.id === id);
}

const user = getById(typedUsers, 2);
console.log(user);

9. Merge or Transform Properties Using the Spread Operator

The spread operator can be used to transform properties while keeping the original object intact.

Example:

// Update a single object while maintaining immutability
const updatedUsers = users.map((user) =>
  user.id === 2 ? { ...user, name: "Robert" } : user
);

console.log(updatedUsers);

10. Handle Large Arrays with .flatMap

When an array of objects includes nested arrays, .flatMap is perfect for flattening arrays while applying map function.

Example:

const userActivities = [
  { id: 1, activities: ["running", "cycling"] },
  { id: 2, activities: ["swimming"] },
  { id: 3, activities: ["hiking", "climbing"] },
];

const flattenedActivities = userActivities.flatMap((user) => user.activities);
console.log(flattenedActivities); // ['running', 'cycling', 'swimming', 'hiking', 'climbing']

11. Handle Arrays with Nested Objects

An object may contain nested objects or nested arrays of related entities.

Example:

interface Employee {
  id: number;
  name: string;
  department: {
    name: string;
    manager: string;
  };
}

const employees: Employee[] = [
  { id: 1, name: "Alice", department: { name: "Engineering", manager: "Bob" } },
  {
    id: 2,
    name: "Charlie",
    department: { name: "Marketing", manager: "John" },
  },
];

How to Handle:

  • Use optional chaining (?.) to safely access nested properties:
const deptNames = employees.map((emp) => emp.department?.name); // ['Engineering', 'Marketing']
  • Flatten or traverse nested structures using recursion or .flatMap() if necessary.

12. Handle Deeply Nested Arrays

Arrays inside objects, which themselves contain more arrays.

Example:

interface Team {
  name: string;
  members: { id: number; name: string }[];
}

const teams: Team[] = [
  {
    name: "Frontend",
    members: [
      { id: 1, name: "Alice" },
      { id: 2, name: "Bob" },
    ],
  },
  { name: "Backend", members: [{ id: 3, name: "Charlie" }] },
];

How to Handle:

  • Flatten Nested Arrays using .flatMap() or recursion:
const allMembers = teams.flatMap((team) => team.members);

console.log(allMembers);
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }]

13. Handle Arrays Containing Mixed Types

Objects in the array may follow different structures (discriminated union).

Example:

type Payment =
  | { type: "card"; cardNumber: string }
  | { type: "paypal"; email: string };

const payments: Payment[] = [
  { type: "card", cardNumber: "1234 5678 9012 3456" },
  { type: "paypal", email: "user@example.com" },
];

How to Handle:

  • Use TypeScript discriminated unions or runtime checks:
payments.forEach((payment) => {
  if (payment.type === "card") {
    console.log("Card Payment: ", payment.cardNumber);
  } else if (payment.type === "paypal") {
    console.log("PayPal Email: ", payment.email);
  }
});

14. Handle Associative Arrays (Key/Value Maps)

Objects in the array may act as pseudo-dictionary.

Example:

const keyValuePairs = [
  { key: "username", value: "Alice" },
  { key: "email", value: "alice@example.com" },
];

How to Handle:

  • Convert to a normal object for fast lookup:
const dictionary = keyValuePairs.reduce((acc, pair) => {
  acc[pair.key] = pair.value;
  return acc;
}, {});
console.log(dictionary); // { username: 'Alice', email: 'alice@example.com' }

15. Explode Object’s enumerated Key/Value pairs into Arrays of data

Object’s enumerated key/value properties can be exploded into 3 types of arrays ie

  • Array of Object’s individual Key/Value Pairs
  • Array of Object’s Keys Only
  • Array of Object’s Values Only

Example:

const someObjectWithKeyValuePairs = {
  name: "Bob",
  age: "44",
  address: "Address 123",
};

How to Handle:

  • Split object into array of object’s individual key/value pairs:
const arrOfObjectKeyValuePairs = Object.entries(someObjectWithKeyValuePairs).map(([key, value]) => ({
  [key] : value
}))

console.log(arrOfObjectKeyValuePairs): // [{name: "Bob"}, {"age": "44"}, {"address":"Address 123"}]
  • Split object into array of object’s keys only:
const arrOfObjectKeysOnly = Object.keys(arrOfObjectKeyValuePairs);
console.log(arrOfObjectKeysOnly); // ["name", "age", "address"]
  • Split object into array of object’s values only:
const arrOfObjectValuesOnly = Object.values(arrOfObjectKeyValuePairs);
console.log(arrOfObjectValuesOnly); // ["Bob", "44", "Address 123"]

16. Enumerate Array of Object’s Key/Value pairs into Array of either Keys or Values only

Example:

const arrayOfObjectsKeyValuePair = [
  { integer: 1 },
  { double: 1.2 },
  { floating: 123e5 },
];

How to Handle:

  • Fetch only the objects’ keys into new array. To do this, we iterate each item of array, explode item’s keys into array. Since the same array has only one key item, we can only fetch it on the zero-index.
const arrOfKeysOnly = arrayOfObjectsKeyValuePair.map((item) => Object.keys(item)[0])

console.log(arrOfKeysOnly): // ["integer", "double", "floating"]
  • Fetch only the objects’ values into new array. To do this, we iterate each item of array, explode item’s value only into array. Since the same array has only one key item, we can only fetch it on the zero-index.
const arrOfValuesOnly = arrayOfObjectsKeyValuePair.map((item) => Object.values(item)[0])
console.log(arrOValuesOnly): // [1, 1.2, 123e5]

NB: Note this approach looks similar approach #14 and #15, but we reuse them for spliting array of objects into boths arrays of keys and values only respectively

17. Handle Circular References

Objects in the array may reference each other, creating circular dependencies.

Example:

const parent = { id: 1, name: "Parent" };
const child = { id: 2, name: "Child", parent };
parent.child = child; // Circular reference!
const family = [parent, child];

How to Handle:

  • Serialize circular references (e.g., using flatted or a custom replacer):
import { stringify } from "flatted";
console.log(stringify(family));

18. Objects with Dynamic Properties

Each object may contain dynamic keys that are unpredictable.

Example:

const dynamicObjects = [
  { id: 1, meta: { created: "202-01-01", updated: "202-01-10" } },
  { id: 2, meta: { created: "202-05-01", deprecated: true } },
];

How to Handle:

  • Use dot-notation, optional chaining, or dynamic property access:
const createdDates = dynamicObjects.map((obj) => obj.meta?.created);

19. Complex Relationships Between Objects

Objects reference or relate to other objects (relational data).

Example:

const users = [
  { id: 1, name: "Alice", friendIds: [2, 3] },
  { id: 2, name: "Bob", friendIds: [1] },
  { id: 3, name: "Charlie", friendIds: [1] },
];

How to Handle:

  • Lookup relationships by ID:
const userMap = new Map(users.map((user) => [user.id, user]));
const getFriends = (userId: number) => {
  const user = userMap.get(userId);
  return user?.friendIds.map((id) => userMap.get(id));
};

console.log(getFriends(1));
// [{ id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }]

20. API Pagination and Chunked Data

Arrays retrieved from APIs are paginated or chunked into parts.

Example:

const paginatedResponse = {
  data: [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
  ],
  meta: { page: 1, totalPages: 5 },
};

How to Handle:

  • Concatenate paginated data:
let allUsers = [];
for (let page = 1; page <= paginatedResponse.meta.totalPages; page++) {
  const response = fetchData(page); // Simulated API call
  allUsers = allUsers.concat(response.data);
}

21. Arrays with Computed Properties

Objects may include computed or derived properties.

Example:

const products = [
  { id: 1, name: "Laptop", price: 1000 },
  { id: 2, name: "Phone", price: 500 },
];

const productsWithTax = products.map((product) => ({
  ...product,
  priceWithTax: product.price * 1.1, // Add10% tax
}));

Final Thoughts

Handling arrays of objects in JavaScript and TypeScript has never been easier with ECMAScript’s robust methods along TypeScript’s type safety features. Leveraging these modern tools helps me to write concise, maintainable, and efficient code once you have recognised numerous patterns of array data manipulation problems you’re going to deal with over and over again. Along with having solid fundamentals on Javascript arrays and primitive object types at its core gived you ultimate confidence to write succinct and clear Javascript array handling code where possible. Start incorporating these tips and tricks into your next project! It’s been fun learning experience.

Till then, Happy Coding!

Comments