Is there a type-safe way to reduce() a larger object into a new type in typescript?

Viewed 161

I have a data structure that represents the results from a database query, which is an object with many properties, all scalars (in my case, all either strings or numbers). I want to extract a portion of these properties and fill out a new object that has a defined shape.

const input: Record<string, string | number> = { name: 'Jane', age: 42, fav_pet: 'Dog', fav_col: 'Blue', fav_dest: 'Paris' };
const FAVS = ['pet', 'col', 'dest'] as const;
type FavsType = {
  pet: string;
  col: string;
  dest: string;
}

const output: FavsType = FAVS.reduce((acc, key) => ({ ...acc, [key]: input['fav' + key] }), {});
//    ~~~~~~
//    ^^^^^^ Type '{}' is missing the following properties from type 'FavsType': pet, col, dest

The problem is, if I use the reduce method to do this, Typescript is unable to figure out that the return value of the reduce must contain an object in the correct shape. I've also tried using Object.fromEntries(FAVS.map()) with similar results.

Is there any type-safe solution to this which doesn't involve assigning each property explicitly?

Playground Link

1 Answers

Be Typescript. When Typescript encounter the reduce, it have to declare new variables that it have to type.

const output: FavsType = FAVS.reduce((acc, key) => ({
  ...acc,

  [key]: input[`fav${key}`],
}), ------> {} <------);

The accumulator being initialized as {} then typescript infer the type '{}'.

Mutating the accumulator won't change it's type from typescript point of view.

You will have to tell typescript

Trust me, the returned type is of FavsType

That's where assertions comes in :

const output: FavsType = FAVS.reduce((acc, key) => ({
  ...acc,

  [key]: input[`fav${key}`],
}), {}) as FavsType;

Ideally typescript should see the mutation of the object and infer the proper reduce returned type, but it ain't that "smart" yet.

Related