Home > Blockchain >  How to derive an object type with another object's values in Typescript?
How to derive an object type with another object's values in Typescript?

Time:05-14

I'm trying to create a function that will return a generic object type with keys that are derived from an argument passed into that function. A simplified example:

// What do I put here for this to work?
type ObjectB<T> = {};

declare function makeObjectB<T>(a: T): ObjectB<T>;

const objectB = makeObjectB({
  name: "foo"
});

console.log(objectB.foo)

typeof objectB would result in something like:

{
  foo: string
}

Bonus points if the following example could also be supported:

// What do I put here for this to work?
type ObjectB<T> = {};

declare function makeObjectB<T>(a: T[]): ObjectB<T[]>;

const objectB = makeObjectB([{ name: "foo" }, { name: "bar" }]);

console.log(objectB.foo)
console.log(objectB.bar)

typeof objectB => { foo: string; bar: string }

CodePudding user response:

Sure it is, with a mapped type:

type ObjectB<T extends { name: string }> = {
    [K in T["name"]]: string;
};

You will notice the extends { name: string } after T. This is a generic constraint, which is similar to a function parameter type argument.

Inside the mapped type, we go through each K in T["name"] and map it to a string.

So if we had this: ObjectB<{ name: "foo" }>, T["name"] would be "foo" and this type would result in:

{
    foo: string;
}

With this type, we can write our function signature now:

declare function makeObjectB<T extends { name: string }>(a: T): ObjectB<T>;

You will also notice that the same generic constraint is present here. This is to satisfy TypeScript that T from makeObjectB can be used with ObjectB.

And for bonus points, we can define an overload:

declare function makeObjectB<T extends ReadonlyArray<{ name: string }>>(a: T): ObjectB<T[number]>;

T can now be any array of objects that have a name key that points to a string.

The other thing that's different here is using ObjectB<T[number]> instead of just ObjectB<T>. This lets us get all the elements in the array (technically tuple here) as a union, which means T["name"] is now something like "foo" | "bar".

You can try this solution out here.

  • Related