0

0

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

聖光之護

聖光之護

发布时间:2025-11-14 17:48:03

|

824人浏览过

|

来源于php中文网

原创

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语法糖。

Smart Picture
Smart Picture

Smart Picture 智能高效的图片处理工具

下载

构建类型安全的查询函数

假设我们有一个名为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 => {
  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 => {
  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 => {
  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 => {
  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 => {
  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项目中实现高效且类型安全的数据库交互。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

554

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

374

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

731

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

477

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

394

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

991

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

656

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

551

2023.09.20

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

0

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.7万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.2万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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