Skip to content
Advertisement

Typescript dynamically create interface

I use simple-schema to define DB schemas in an object:

{
   name: 'string',
   age: 'integer',
   ...
}

Is it somehow possible to create an interface or class from this object, so I don’t have to type everything twice?

Advertisement

Answer

You can do this, but it might be more trouble than it’s worth unless you think you might be changing the schema. TypeScript doesn’t have built-in ways of inferring types in a way that you want, so you have to coax and cajole it to do so:


First, define a way of mapping the literal names 'string' and 'integer' to the TypeScript types they represent (presumably string and number respectively):

type MapSchemaTypes = {
  string: string;
  integer: number;
  // others?
}

type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
  -readonly [K in keyof T]: MapSchemaTypes[T[K]]
}

Now if you can take an appropriately typed schema object like the one you specified, and get the associated type from it:

const personSchema = {name: 'string', age: 'integer'}; 
type Person = MapSchema<typeof personSchema>; // ERROR

Oops, the problem is that personSchema is being inferred as {name: string; age: string} instead of the desired {name: 'string'; age: 'integer'}. You can fix that with a type annotation:

const personSchema: { name: 'string', age: 'integer' } = { name: 'string', age: 'integer' }; 
type Person = MapSchema<typeof personSchema>; // {name: string; age: number};

But now it feels like you’re repeating yourself. Luckily there is a way to force it to infer the proper type:

function asSchema<T extends Record<string, keyof MapSchemaTypes>>(t: T): T {
  return t;
}
const personSchema = asSchema({ name: 'string', age: 'integer' }); // right type now
type Person = MapSchema<typeof personSchema>; // {name: string; age: number};

UPDATE 2020-06: in more recent TS versions you can use a const assertion to get the same result:

const personSchema = { name: 'string', age: 'integer' } as const;
type Person = MapSchema<typeof personSchema>;

That works!


See it in action on the Typescript Playground. Hope that helps; good luck!

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement