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.