Back

Applying a transformation to all object properties in TypeScript

In this guide, we'll learn how to transform object properties in TypeScript when using Object.entries() and reduce().

Let’s consider the following JavaScript object:

type PreTax = Record<string, { preTaxValue: number }>;

const preTaxBill: PreTax | undefined = {
  meal: { preTaxValue: 100 },
  drink: { preTaxValue: 50 },
};

The preTaxBill is optionally undefined to demonstrate how to handle such scenarios.

Our goal is to transform the object by adding a new property: the post-tax value. After the transformation, the object should conform to the following type:

type PostTax = Record<string, { preTaxValue: number; postTaxValue: number }>;

Writing the transformation

Here’s how we can implement the transformation:

type PreTax = Record<string, { preTaxValue: number }>;

type PostTax = Record<string, { preTaxValue: number; postTaxValue: number }>;

const preTaxBill: PreTax | undefined = {
  meal: { preTaxValue: 100 },
  drink: { preTaxValue: 50 },
};

const postTaxBill = (
  Object.entries(preTaxBill || {}) as Array<[keyof PreTax, PreTax[keyof PreTax]]>
).reduce(
  (acc, [key, value]) => ({
    ...acc,
    [key]: {
      ...value,
      postTaxValue: value.preTaxValue * 1.2, // Example tax computation
    },
  }),
  {} as PostTax
);

Algorithm explanation

Here’s a step-by-step breakdown:

1. Extracting Key-Value pairs:

Using Object.entries(), we create an array of [key, value] pairs from the object. For preTaxBill, this results in: [['meal', { preTaxValue: 100 }], ['drink', { preTaxValue: 50 }]].

2. Transforming with reduce():

We use reduce() to iterate over this array and build a new object. During each iteration, we:

  • Copy the existing accumulator object.
  • Add a new entry for the current key, including both the original properties and the computed postTaxValue.

Type considerations

Handling undefined

Since preTaxBill can be undefined, we provide an empty object ({}) as a fallback to Object.entries(). This avoids runtime errors and ensures the code handles empty objects gracefully.

Ensuring type-safety

Object.entries() type annotation:

By default, Object.entries() returns an array of Array<[string, any]>. To maintain type safety, we explicitly cast it as Array<[keyof PreTax, PreTax[keyof PreTax]]>. This ensures type consistency, especially if the transformation logic expects specific types.

Accumulator initialization:

We specify that the initial accumulator object ({}) is of type PostTax to help TypeScript infer the final result's type correctly.

Conclusion

This approach applies a transformation across all properties of the object while ensuring full type safety. By leveraging TypeScript's robust type system, we prevent runtime errors and maintain clear, predictable behavior.

Last updated on December 12, 2024.

👋 About the author

I'm Laurent, a freelance developer experience engineer that helps dev tools build great onboarding experiences.

I specialize in technical writing, UX writing, and full-stack development.

✍️ Latest posts

Read my other articles.

💌 Get in touch

Find me on Bluesky, X (formerly Twitter), LinkedIn, and Github.

You can also contact me via email.