javascript - Typescript crear interfaz dinámicamente
Uso simple-schema para definir esquemas de base de datos en un objeto:
{
name: 'string',
age: 'integer',
...
}
¿Es de alguna manera posible crear una interfaz o clase a partir de este objeto, para que no tenga que escribir todo dos veces?
Respuesta #1
Puedes hacer esto, pero podría ser más problema que beneficio a menos que creas que podrías estar cambiando el esquema. TypeScript no tiene formas integradas de inferir tipos de la manera que deseas, así que tienes que persuadirlo y convencerlo para que lo haga:
Primero, define una forma de asignar los nombres literales 'string' y 'integer' a los tipos de TypeScript que representan (presumiblemente string y number respectivamente):
type MapSchemaTypes = {
string: string;
integer: number;
// others?
}
type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
-readonly [K in keyof T]: MapSchemaTypes[T[K]]
}
Ahora, si puedes tomar un objeto de esquema con el tipo apropiado como el que especificaste, y obtener de él el tipo asociado:
const personSchema = {name: 'string', age: 'integer'};
type Person = MapSchema<typeof personSchema>; // ERROR
Ups, el problema es que personSchema se está infiriendo como {name: string; age: string} en lugar del deseado {name: 'string'; age: 'integer'}. Puedes arreglar eso con una anotación de tipo:
const personSchema: { name: 'string', age: 'integer' } = { name: 'string', age: 'integer' };
type Person = MapSchema<typeof personSchema>; // {name: string; age: number};
Pero ahora parece que te estás repitiendo. Afortunadamente, hay una manera de obligarlo a inferir el tipo correcto:
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};
ACTUALIZACIÓN 2020-06: en versiones más recientes de TS puedes usar una aserción const para obtener el mismo resultado:
const personSchema = { name: 'string', age: 'integer' } as const;
type Person = MapSchema<typeof personSchema>;
¡Eso funciona!
Véalo en acción en el Playground de Typescript. Espero que eso ayude; ¡buena suerte!
Respuesta #2
No creo que puedas declarar interfaces dinámicas. Sin embargo, puedes crear un tipo para objetos con propiedades conocidas.
Puedes crear un objeto que asigne literales de cadena a tipos reales, por ejemplo 'integer' => number, pero eso no es relevante para la pregunta. No sé qué framework estás usando, pero el siguiente ejemplo funciona para un framework de aspecto similar: Mongoose.
usuarios.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 } ;
uso :
import { User, Users } from './user';
Users.find({}).exec((err: Error, res: User) => { ... })
El resultado devuelto debe tener las mismas claves que UserSchema, pero todos los valores se asignan a any, ya que todavía tendrías que mapear los literales de cadena a tipos.
