Reputation: 1070
Lest say I have this type
type foo = {
go: string;
start: string;
}
How can I dynamicaly made a function that will return
{ go: '', start: '' }
Is there any way on Type Script we could dynamically generate empty object based on solely just type? Or this is impossible because we cant just loop
I was thinking something like these
function generate<T>(T)<T> {
const obj = {}
for (const key of keyof T) {
obj[key] = ''
}
return obj
}
Upvotes: 8
Views: 14447
Reputation: 327819
When you run the TypeScript compiler (tsc
) to transpile TypeScript code into runnable JavaScript, the language's static type system is erased. So your Foo
type (renamed to uppercase to meet TS naming conventions) is not present in any form at runtime. There is nothing you can iterate over to get the keys go
and start
.
The most straightforward way to get something to happen at runtime is to write the JavaScript necessary to do it, and then to make sure the TypeScript compiler can give you the strong types you need while you're writing it. This is basically the reverse of what you're trying to do.
In your case: what does generate()
need in order to work at runtime? Well, if we can assume that the values generated by generate()
will be objects holding only string
-valued properties, then we need to pass it a list of the keys of the object. So what if we write generate()
to do that, and then define Foo
in terms of the output of generate()
instead of the other way around?
For example:
function generate<K extends PropertyKey>(...keys: K[]) {
return Object.fromEntries(keys.map(k => [k, ""])) as { [P in K]: string };
}
const myFooObject = generate("go", "start");
type Foo = typeof myFooObject;
/* type Foo = {
go: string;
start: string;
} */
console.log(myFooObject)
/* {
"go": "",
"start": ""
} */
Here generate()
is a generic function that takes a list of keys (of type K
) and produces a value of a type with keys in K
and values of type string
. That {[P in K]: string}
is a mapped type equivalent to Record<K, string>
using the Record<K, V>
utility type.
The implementation uses Object.fromEntries()
to build the object, and the return type is asserted to be the right type because TypeScript sees Object.fromEntries()
as returning a type that's too wide for our purposes.
Anyway when you call const myFooObject = generate("go", "start")
, it produces a value of type {go: string; start: string}
, which is the same as your Foo
type. So we can just define Foo
as type Foo = typeof myFooObject
instead of doing it manually. You could still do it manually, but the point I'm showing here is that it's much easier to write DRY code in TypeScript if you start with values and generate types from them, instead of trying to do it the other way around.
Again, if you are using the TypeScript compiler tsc
as-is, then type erasure prevents you from writing generate()
from the definition of Foo
. But...
If you are willing to add a build step to your project and perform code generation using the TypeScript compiler API or something like it, you can then do arbitrary things with types. There are libraries that do similar things to what you want; for example, ts-auto-mock claims to generate mock objects given an object type, which looks like exactly your use case.
Such an extra build step might be a reasonable approach for you, but if you go down that route you should note that you're not using just plain TypeScript anymore (and therefore the topic is probably out of scope for a question with just a TypeScript tag).
Upvotes: 5
Reputation: 4049
You could make it generic, like this:
type foo<T extends string = string> = {
go: T;
start: T;
}
const test: foo<''> = {
go: '',
start: '',
};
Upvotes: 2