首页 > web前端 > js教程 > 正文

TypeScript中将SQLite查询结果反序列化为类型化对象的教程

聖光之護
发布: 2025-11-14 17:48:03
原创
797人浏览过

typescript中将sqlite查询结果反序列化为类型化对象的教程

本教程将指导您如何在TypeScript应用中,特别是使用`sqlite3`库时,将从SQLite数据库查询到的原始数据行高效且类型安全地反序列化为预定义的TypeScript类实例。文章重点讲解了`sqlite3.all()`方法的异步特性、Promise的正确使用方式,以及如何迭代并映射数据库返回的行数据以构建类型化的对象数组,确保数据处理的健壮性和可维护性。

引言:数据库结果与类型安全

在现代TypeScript应用开发中,与数据库交互是常见任务。从数据库中检索数据后,我们通常希望将其转换为应用程序中定义的、具有明确结构的TypeScript对象或接口实例,以利用TypeScript的类型检查优势,提高代码的可读性、可维护性和健壮性。

然而,像sqlite3这样的数据库驱动程序,其查询方法(例如all())通常返回的是原始的、松散类型的数据行数组。直接使用这些原始数据往往会导致类型不明确,甚至运行时错误。此外,数据库操作本质上是异步的,这要求我们采用适当的异步编程模式来处理结果。本教程将以sqlite3为例,详细讲解如何克服这些挑战,实现从SQLite查询结果到TypeScript类型化对象的无缝反序列化。

理解 sqlite3.all() 方法的异步特性

sqlite3库中的all()方法用于执行SQL语句并获取所有匹配的行。然而,它并不是一个同步方法,它不会立即返回查询结果。根据node-sqlite3的API文档,all()方法提供的是一个回调式API:它接受一个回调函数作为参数,当查询完成时,该回调函数会被调用,并传入可能发生的错误和查询结果行。

值得注意的是,all()方法本身的返回值是Statement对象,这允许进行链式调用,但它并不是我们期望的数据结果。因此,直接在all()调用之后尝试访问结果是无效的,因为查询可能尚未完成。

为了在TypeScript/JavaScript环境中优雅地处理这种异步行为,我们应该将其封装在一个Promise中,或者使用async/await语法糖。

序列猴子开放平台
序列猴子开放平台

具有长序列、多模态、单模型、大数据等特点的超大规模语言模型

序列猴子开放平台 0
查看详情 序列猴子开放平台

构建类型安全的查询函数

假设我们有一个名为Obj的TypeScript接口或类,它代表了数据库中ObjTable表的结构:

interface Obj {
  id: number;
  name: string;
  amount: number;
}
登录后复制

我们的目标是编写一个函数,能够从ObjTable中读取所有行,并将它们转换为Obj类型的数组。

1. 使用 Promise 封装异步操作

由于sqlite3.all()是异步的,我们需要使用Promise来封装它,以便能够以同步的方式处理其结果(例如使用.then()或await)。

import * as sqlite3 from 'sqlite3';

// 假设 db 是已初始化的 sqlite3 数据库实例
const db = new sqlite3.Database(':memory:'); // 示例:使用内存数据库

// 创建表(为完整示例提供)
export const CreateObjTable = (): Promise<void> => {
  return new Promise((resolve, reject) => {
    const query = db.prepare(`
      CREATE TABLE IF NOT EXISTS ObjTable
      (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        amount INTEGER
      )
    `);
    query.run((err) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  });
};

// 获取所有 Obj 对象的函数
export const GetAllObjs = (): Promise<Obj[]> => {
  const query = db.prepare("SELECT * FROM ObjTable");

  return new Promise((resolve, reject) => {
    let objs: Obj[] = []; // 初始化一个空数组来存储类型化的对象

    // 调用 query.all(),并传入回调函数
    query.all((err, rows) => {
      if (err) {
        // 如果发生错误,拒绝 Promise
        return reject(err);
      }

      // 确保 rows 是一个数组,并正确迭代
      // rows 的类型通常是 any[],我们需要将其断言为 Obj[] 或在内部手动映射
      for (const row of rows as any[]) { // 使用 for...of 迭代数组元素
        objs.push({
          id: row.id,
          name: row.name,
          amount: row.amount,
        } as Obj); // 将原始行数据映射到 Obj 类型
      }

      // 查询成功且数据映射完成,解决 Promise 并返回类型化数组
      resolve(objs);
    });
  });
};
登录后复制

2. 核心逻辑解析

  • return new Promise((resolve, reject) => { ... });: 这是将回调式API转换为Promise式API的标准模式。resolve函数用于在操作成功时返回结果,reject函数用于在操作失败时返回错误。
  • query.all((err, rows) => { ... });: 这是sqlite3.all()方法的回调函数。
    • err: 如果查询过程中发生错误,此参数将包含错误对象。我们应该检查它并在存在错误时调用reject(err)。
    • rows: 这是一个数组,其中包含查询返回的所有数据行。每一行都是一个普通JavaScript对象,其属性名对应于SQL查询中的列名。
  • for (const row of rows as any[]) { ... }:
    • for...of vs for...in: 原始问题中使用了for...in,它会迭代对象的(对于数组来说是索引),而不是值。例如,for(const row in rows)会使row变量依次为"0", "1", "2"等字符串。正确的做法是使用for...of来直接迭代数组的元素
    • rows as any[]: rows参数的实际类型通常是any[],因为它是一个通用的数据结构。我们可以将其断言为any[],然后在循环内部将每个row对象映射到我们预期的Obj类型。
  • objs.push({ id: row.id, name: row.name, amount: row.amount } as Obj);:
    • 在循环内部,row变量现在代表了数据库中的一行数据(一个普通的JavaScript对象)。我们可以直接通过属性名(例如row.id、row.name)访问其列值。
    • 我们创建一个新的Obj字面量对象,将row的属性值赋给它,并通过as Obj进行类型断言,确保我们正在构建一个符合Obj接口的对象。
  • resolve(objs);: 当所有行都被成功处理并映射到Obj数组后,我们调用resolve(objs)来完成Promise,并将最终的类型化对象数组返回给调用者。

示例代码

为了提供完整的上下文,以下是包含表创建、数据插入和数据查询的完整示例:

import * as sqlite3 from 'sqlite3';

// 定义数据模型
interface Obj {
  id: number;
  name: string;
  amount: number;
}

// 假设 db 是已初始化的 sqlite3 数据库实例
// 实际应用中,你可能从外部配置或单例模式获取 db 实例
const db = new sqlite3.Database(':memory:', (err) => {
  if (err) {
    console.error('Error opening database', err.message);
  } else {
    console.log('Connected to the SQLite database.');
  }
});

// 创建 ObjTable 表
export const CreateObjTable = (): Promise<void> => {
  return new Promise((resolve, reject) => {
    db.run(`
      CREATE TABLE IF NOT EXISTS ObjTable
      (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        amount INTEGER
      )
    `, (err) => {
      if (err) {
        reject(err);
      } else {
        console.log('ObjTable created or already exists.');
        resolve();
      }
    });
  });
};

// 插入示例数据
export const InsertObj = (name: string, amount: number): Promise<void> => {
  return new Promise((resolve, reject) => {
    db.run(`INSERT INTO ObjTable (name, amount) VALUES (?, ?)`, [name, amount], function(err) {
      if (err) {
        reject(err);
      } else {
        console.log(`A row has been inserted with rowid ${this.lastID}`);
        resolve();
      }
    });
  });
};

// 获取所有 Obj 对象的函数
export const GetAllObjs = (): Promise<Obj[]> => {
  return new Promise((resolve, reject) => {
    const objs: Obj[] = []; // 初始化一个空数组来存储类型化的对象

    db.all("SELECT id, name, amount FROM ObjTable", (err, rows: any[]) => {
      if (err) {
        return reject(err); // 如果发生错误,拒绝 Promise
      }

      // 迭代查询结果,将原始行数据映射到 Obj 类型
      for (const row of rows) {
        objs.push({
          id: row.id,
          name: row.name,
          amount: row.amount,
        });
      }

      resolve(objs); // 查询成功且数据映射完成,解决 Promise
    });
  });
};

// 示例使用
async function main() {
  try {
    await CreateObjTable();
    await InsertObj('Item A', 100);
    await InsertObj('Item B', 250);
    await InsertObj('Item C', 75);

    const allObjs = await GetAllObjs();
    console.log('Retrieved Objects:', allObjs);

    // 验证类型安全
    allObjs.forEach(obj => {
      console.log(`ID: ${obj.id}, Name: ${obj.name}, Amount: ${obj.amount}`);
      // obj.nonExistentProperty = 'error'; // 这会在编译时报错,体现了类型安全
    });

  } catch (error) {
    console.error('An error occurred:', error);
  } finally {
    db.close((err) => {
      if (err) {
        console.error('Error closing database', err.message);
      } else {
        console.log('Database connection closed.');
      }
    });
  }
}

main();
登录后复制

注意事项与最佳实践

  1. 异步处理优先: 始终将数据库操作视为异步任务。在现代TypeScript/JavaScript中,推荐使用async/await语法来编写更具可读性的异步代码,它在底层依然是基于Promise的。
  2. 错误处理: 在Promise的reject回调中捕获并处理数据库操作可能出现的错误。在async/await模式下,可以使用try...catch块。
  3. 类型断言与数据校验: 尽管as Obj可以帮助编译器理解数据类型,但它并不能在运行时提供真正的类型校验。如果数据源不可信(例如来自外部API),在映射之前进行运行时数据校验(如使用Zod、Joi等库)是更健壮的做法。
  4. SQL 注入防护: 在插入或更新数据时,务必使用参数化查询(如示例中的db.run(sql, [value1, value2])),而不是直接拼接字符串,以防止SQL注入攻击。
  5. ORM/Query Builder: 对于更复杂的数据库交互和大型项目,考虑使用ORM(Object-Relational Mapping)库(如TypeORM, Prisma)或查询构建器(如Knex.js)。它们可以进一步抽象数据库操作,提供更高级的类型安全和开发效率。
  6. 数据库连接管理: 确保数据库连接的正确打开和关闭。在生产环境中,通常会使用连接池来管理数据库连接,提高性能和资源利用率。

总结

将SQLite查询结果反序列化为TypeScript类型化对象是构建健壮、可维护应用程序的关键一步。通过深入理解sqlite3.all()等方法的异步特性,并结合Promise的封装、for...of的正确迭代以及明确的类型映射,我们可以有效地将原始数据库数据转换为我们应用程序所需的强类型结构。遵循本教程中的指导和最佳实践,将有助于您在TypeScript项目中实现高效且类型安全的数据库交互。

以上就是TypeScript中将SQLite查询结果反序列化为类型化对象的教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号