如何将TypeScript接口键转换为类型值,实现自动建表推断?
基于TypeScript接口自动生成数据库表结构的实现方案
我正尝试在数据库表类中实现进阶功能,希望用户仅传入行接口(必要时可传类),让表类自动推断如何创建数据库表。相关代码示例如下:
interface Row { rowid: number } class Table<T extends Row> { getRowDefinition(){ // 这里需要实现用T生成列定义的逻辑 } createTable(){ const defs = this.getRowDefinition(); const cols = defs.map(def => { switch(def.type){ case 'string': return `${def.name} TEXT`; case 'number': return `${def.name} INTEGER`; } }); const sql = 'CREATE TABLE ...'; // 拼接cols生成完整SQL } } interface MyRow extends Row { name: string age: number } class MyTable extends Table<MyRow> {}
核心问题说明
TypeScript的类型信息仅存在于编译阶段,泛型参数T在编译后会被完全擦除,所以无法直接通过泛型T在运行时获取行结构的字段和类型信息。要实现自动推断,必须借助运行时可访问的元数据,以下是两种可行的实现方案:
方案一:手动传入字段元数据
既然接口没有运行时表示,我们可以让用户在创建Table实例时,手动传入字段的类型定义对象,以此作为表结构的依据:
interface Row { rowid: number } // 定义列结构的描述类型 interface ColumnDefinition { name: string; type: 'string' | 'number'; } class Table<T extends Row> { private columnDefs: ColumnDefinition[]; constructor(columnDefs: ColumnDefinition[]) { // 确保rowid字段被包含(如果用户没传的话自动添加) const hasRowid = columnDefs.some(def => def.name === 'rowid'); this.columnDefs = hasRowid ? columnDefs : [...columnDefs, { name: 'rowid', type: 'number' }]; } createTable(tableName: string) { const cols = this.columnDefs.map(def => { switch(def.type){ case 'string': return `${def.name} TEXT`; case 'number': return `${def.name} INTEGER`; default: throw new Error(`不支持的字段类型:${def.type}`); } }); const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (${cols.join(', ')})`; console.log(sql); // 这里可以添加执行SQL的实际逻辑 } } interface MyRow extends Row { name: string; age: number; } // 创建实例时传入字段元数据 const myTable = new Table<MyRow>([ { name: 'name', type: 'string' }, { name: 'age', type: 'number' } ]); myTable.createTable('my_table');
方案二:使用类+装饰器标记字段
如果允许用户用类代替接口,我们可以通过装饰器来标记每个字段的类型,在运行时从类的构造函数中提取这些元数据:
interface Row { rowid: number; } // 装饰器工厂:用于标记字段对应的数据库类型 function Column(type: 'string' | 'number') { return function(target: any, propertyKey: string) { // 将字段定义挂载到类的构造函数上 if (!target.constructor.columnDefs) { target.constructor.columnDefs = []; } target.constructor.columnDefs.push({ name: propertyKey, type }); }; } class Table<T extends Row> { private columnDefs: { name: string; type: 'string' | 'number' }[]; constructor(rowClass: new () => T) { // 合并默认的rowid字段和类中标记的字段 this.columnDefs = [ { name: 'rowid', type: 'number' }, ...(rowClass.columnDefs || []) ]; } createTable(tableName: string) { const cols = this.columnDefs.map(def => { switch(def.type){ case 'string': return `${def.name} TEXT`; case 'number': return `${def.name} INTEGER`; default: throw new Error(`不支持的字段类型:${def.type}`); } }); const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (${cols.join(', ')})`; console.log(sql); // 这里可以添加执行SQL的实际逻辑 } } // 用类代替接口,通过装饰器标记字段类型 class MyRow implements Row { rowid: number; @Column('string') name: string; @Column('number') age: number; } // 传入类的构造函数即可生成表 const myTable = new Table(MyRow); myTable.createTable('my_table');
内容的提问来源于stack exchange,提问作者samala7800
相关产品推荐
相关产品推荐

