Sergey Shevchenko
Sergey Shevchenko

Reputation: 570

Is it possible to define object type with dynamic set of keys

Suppose I have a function that accepts an array of string as an argument and builds an object that has each of the array item as a key. Like this:

function buildObject(keys: string[]) {
  let obj = {};
  for (let i = 0; i < keys.length; i++) {
    obj[keys[i]] = i;
  }
  return obj;
}

Is it possible to define a return type for this function so that attempt to access object key that is not defined would result in compiler error?

const obj = buildObject(['a', 'b', 'b']);
console.log(obj.a) // OK
console.log(obj.d) // I want a compiler error here

Upvotes: 0

Views: 95

Answers (2)

Igor Vanian
Igor Vanian

Reputation: 390

I'm not very experienced with TS so I tried to figure it out and I will try to explain. I tried something like this.

My thought process is, you need to make buildObject signature infer from your input but you need to tell TS what kind of input you have. Obviously an array of strings.

function buildObject<T extends string>(keys: T[]) {}

Then, you need to tell what's the return type (your object). Here, you need to use a mapped type so your object has keys that are type of each literal of your array of keys. I also added ? so your initialization to {} works.

type Obj<T extends string> = {
  [key in T]?: number;
};

Full code:

type Obj<T extends string> = {
  [key in T]?: number;
};

function buildObject<T extends string>(keys: T[]) {
  let obj: Obj<T> = {};
  for (let i = 0; i < keys.length; i++) {
    obj[keys[i]] = i;
  }
  return obj;
}

const obj = buildObject(['a', 'b', 'b']);
console.log(obj.a); // OK
console.log(obj.d); // Compiler error

Upvotes: 0

vighnesh153
vighnesh153

Reputation: 5388

You can make the keys array as readonly and then use the type of its values as keys for the result object.

function buildObject<T extends string>(keys: readonly T[]): Record<T, number> {
  let obj = {} as Record<T, number>;
  for (let i = 0; i < keys.length; i++) {
    obj[keys[i]] = i;
  }
  return obj;
}

const obj = buildObject(['a', 'b', 'b']);
console.log(obj.a) // OK
console.log(obj.b) // OK
console.log(obj.d) // Error

Playground link: https://tsplay.dev/wEY64w

Upvotes: 2

Related Questions