Typescript dynamically create interface

后端 未结 2 1012
遥遥无期
遥遥无期 2020-12-06 01:20

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

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

Is it somehow possible to creat

相关标签:
2条回答
  • 2020-12-06 01:53

    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!

    0 讨论(0)
  • 2020-12-06 01:53

    I don't think you can declare dynamic interfaces. However, you can create a type for objects with known properties.

    You can create an object that maps string literals to actual types, e.g. 'integer' => number, but that is not relevant to the question. I don't know what framework you're using but the following example works for a similar looking framework: Mongoose.

    users.js

    export const UserSchema = mongoose.Schema({
        name: String,
        value: Number
    });
    
    export const Users = mongoose.Model('users', UserSchema);
    
    export type User = { [K in keyof typeof UserSchema]: any } ;
    

    usage:

    import { User, Users } from './user';
    
    Users.find({}).exec((err: Error, res: User) => { ... })
    

    The returned result should have the same keys as UserSchema, but all values are mapped to any as you would still have to map string literals to types.

    0 讨论(0)
提交回复
热议问题