在TypeScript项目中使用SQLite进行数据查询时,默认返回的结果往往是缺乏类型约束的普通对象,无法直接享受TypeScript的类型校验和自动补全能力。将查询结果反序列化为类型化对象,是解决这一问题的核心方案。

SQLite默认查询结果的类型问题
使用常见的SQLite库如better-sqlite3执行查询时,返回的行数据默认是Record<string, unknown>类型的对象,没有明确的字段类型定义。比如查询用户表的数据:
import Database from 'better-sqlite3';
// 定义数据库连接
const db = new Database(':memory:');
// 创建用户表并插入测试数据
db.exec(`
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER NOT NULL,
email TEXT
);
INSERT INTO users (name, age, email) VALUES ('张三', 25, 'zhangsan@ipipp.com');
`);
// 执行查询,默认返回类型无约束
const rawResult = db.prepare('SELECT * FROM users').all();
// rawResult的类型是unknown[],访问id、name等字段时TypeScript会报错
console.log(rawResult[0].name); // 类型报错,无法确认name属性存在
手动字段映射实现反序列化
对于字段较少、结构简单的查询结果,手动映射是最直接的方式。先定义目标类型,再遍历原始结果逐个字段赋值。
步骤实现
- 定义与数据库表结构对应的TypeScript接口
- 编写映射函数,将原始对象的字段转换为接口类型的对象
- 对查询结果执行映射操作
// 定义用户类型接口
interface User {
id: number;
name: string;
age: number;
email: string | null;
}
// 手动映射函数
function mapRawToUser(raw: Record<string, unknown>): User {
return {
id: raw.id as number,
name: raw.name as string,
age: raw.age as number,
email: raw.email as string | null
};
}
// 执行查询并反序列化
const rawUsers = db.prepare('SELECT * FROM users').all() as Record<string, unknown>[];
const typedUsers = rawUsers.map(mapRawToUser);
// 此时typedUsers是User类型数组,可享受类型校验
console.log(typedUsers[0].name); // 输出 张三,无类型报错
泛型工具函数通用化反序列化
当有多个不同的表需要反序列化时,手动编写每个映射函数会存在大量重复代码。可以封装一个通用的泛型工具函数,适配不同的类型需求。
// 通用反序列化工具函数,T为目标类型
function deserialize<T extends Record<string, unknown>>(
rawArray: Record<string, unknown>[],
mapper: (raw: Record<string, unknown>) => T
): T[] {
return rawArray.map(mapper);
}
// 复用之前的User映射函数
const userList = deserialize(rawUsers, mapRawToUser);
console.log(userList[0].age); // 输出 25,类型正确
借助第三方库简化反序列化
如果查询结果的字段和接口字段存在命名差异(比如数据库字段是user_name,接口字段是userName),手动映射会比较繁琐。可以使用class-transformer这类库,通过装饰器配置字段映射规则。
import { Type, plainToClass } from 'class-transformer';
// 定义带装饰器的实体类
class UserEntity {
id!: number;
@Type(() => String)
name!: string;
@Type(() => Number)
age!: number;
@Type(() => String)
email!: string | null;
}
// 使用plainToClass完成反序列化
const entityUsers = plainToClass(UserEntity, rawUsers);
console.log(entityUsers[0] instanceof UserEntity); // 输出 true
console.log(entityUsers[0].name); // 输出 张三
不同方案的选择建议
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 手动映射 | 字段少、结构简单、无命名差异 | 无额外依赖,逻辑清晰 | 重复代码多,字段多时维护成本高 |
| 泛型工具函数 | 多表查询、结构相似的反序列化需求 | 代码复用性高,减少重复逻辑 | 仍需手动编写每个类型的映射规则 |
| 第三方库辅助 | 字段多、存在命名差异、需要复杂转换 | 配置灵活,支持复杂转换规则 | 需要引入额外依赖,增加项目体积 |
注意事项
反序列化时需要注意数据库字段类型和TypeScript类型的对应关系,比如SQLite的INTEGER类型对应TypeScript的number,TEXT对应string,BLOB对应Uint8Array,避免类型转换错误。
如果查询结果中包含日期类型的字段,需要在映射时手动将时间戳或字符串转换为Date对象,保证类型正确性。
TypeScriptSQLite反序列化类型化对象数据库查询修改时间:2026-07-07 00:24:28