Skip to content
Advertisement

TypeScript can’t assign an Object Type to Record

I’m learning TypeScript, and decided to try implement it in a small portion of my codebase to get the ball rolling. Specifically, what I’m refactoring now is related to a fixture “factory” for the purpose of generating fixtures for Jest tests.

In addition to these factories, which spit out certain Objects, I also have some helper methods that make things like generating multiple objects a bit easier.

A factory is fairly simple, it looks something like this (the values are spoofed with faker.js):

function channelFactory(): ChannelItem {
  return { foo: "bar" }
}

A ChannelItem is just a simple Object containing some keys

interface ChannelItem { foo: string; }

And as an example of one of those helper methods, I have a createMany function that takes in a Factory function and a Count as arguments

function createMany(factory: () => Record<string, unknown>, count = 1): Record<string, any>[] {
  // A for loop that calls the factory, pushes those into an array and returns that array
}

However, if I try to use these factories somewhere, for example in this function that persists some created channels into the DB, I get the TS compiler warning me about Record<string, any>[] not being assignable to ChannelItem[].

function saveChannels(payload: ChannelItem[]): void { // Unimportant implementation details }
const items = createMany(channelFactory, 5);
saveChannels(items) // => Argument type Record<string, any>[] is not assignable to parameter type ChannelItem[]   Type Record<string, any> is not assignable to type ChannelItem 

I know this is a commonly known issue with Interfaces specifically (Issue #15300) and that the potential solution would be to declare a type rather than an interface, however in this situation I still get the same warning.

type ChannelItem = { foo: string } // Still gives me the above warning

What would the ideal way of making my factory functions more generic here be?

Advertisement

Answer

You could make the createMany function generic:

function createMany<K extends string, T>(factory: () => Record<K, T>, count = 1): Record<K, T>[] {
  const arr = [];
  for (let i = 0; i < count; i++) {
    arr.push(factory());
  }
  return arr;
}

const items = createMany(channelFactory, 5);

console.log(items);
// Prints:
//[
//  { foo: 'bar' },
//  { foo: 'bar' },
//  { foo: 'bar' },
//  { foo: 'bar' },
//  { foo: 'bar' }
//]

I made K extends string because you specified you want your record to have string keys. T can be anything you want.

Just have to fill in the functions yourself, not sure what you want done in those.

Advertisement