mandaputtra
mandaputtra

Reputation: 1070

Create object based on types typescript

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

Answers (2)

jcalz
jcalz

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).

Playground link to code

Upvotes: 5

Brooke Hart
Brooke Hart

Reputation: 4049

You could make it generic, like this:

type foo<T extends string = string> = {
    go: T;
    start: T;
}

const test: foo<''> = {
    go: '',
    start: '',
};

TypeScript Playground

Upvotes: 2

Related Questions