自定义解码器实战:让 deno-postgres 按你的规则解析查询结果
【免费下载链接】postgresPostgreSQL driver for Deno项目地址: https://gitcode.com/gh_mirrors/postgr/postgres
deno-postgres 是 Deno 生态中最流行的 PostgreSQL 驱动之一,它把数据库返回的原始字符串自动解析成 JavaScript 原生类型,让开发者几乎感觉不到类型转换的存在。但默认的解析规则并不总能满足业务需求——比如你想把布尔值转成 0/1、把日期直接变成 dayjs 对象、或者给 JSONB 字段做二次加工。这时,deno-postgres 自定义解码器(custom decoders)就是你最趁手的工具。本文将用最少的代码,带你从零掌握自定义解码器的配置方法、优先级规则和数组类型处理技巧。
为什么你需要自定义解码器?3 个真实场景
默认情况下,deno-postgres 会把bool解析成布尔值、int4解析成数字、jsonb解析成对象(源码见query/decoders.ts)。但实际项目中,你往往会遇到这些痛点:
- 😫格式不统一:想让所有日期字段统一转成 dayjs 对象,方便链式操作;
- 🤔语义不匹配:把数据库的
t/f布尔值映射成业务需要的1/0甚至中文字符串; - 🧹数据需要预处理:查询结果里的 JSON 字段希望自动脱敏、补默认值或改字段名。
自定义解码器能让你在数据到达业务代码之前,就按自己的规则完成转换,一处配置、全局生效,省去每处查询后的重复加工。
快速上手:第一个自定义解码器只需 5 行配置
自定义解码器通过客户端配置项controls.decoders注册,它是一个"类型 → 解码函数"的映射表。来看一个最简单的例子:
import { Client, Oid } from "./mod.ts"; const client = new Client({ database: "some_db", user: "some_user", controls: { decoders: { // 方式一:用类型名作为 key bool: (value: string) => value === "t" ? 1 : 0, // 方式二:用 OID 数字作为 key(16 就是 bool 的 OID) [Oid.bool]: (value: string) => value === "t" ? 1 : 0, }, }, }); await client.connect(); const { rows } = await client.queryObject("SELECT 1 = 1 AS ok"); console.log(rows); // [{ ok: 1 }]解码函数的签名固定为(value: string, oid: number, parseArray) => unknown:第一个参数是数据库返回的原始字符串,第二个是列类型的 OID 编号,第三个是数组解析辅助函数(后面会用到)。Oid对象从mod.ts中导出,完整类型清单见query/oid.ts。
用类型名还是 OID 编号?推荐这样选
deno-postgres 支持两种 key 写法,各有适用场景:
| 写法 | 示例 | 适用场景 |
|---|---|---|
| 类型名 | bool、int4、jsonb | 代码可读性好,推荐日常使用 |
| OID 数字 | 16、23、3802 | 处理自定义类型、枚举类型等冷门类型时更精准 |
小提示:当同一个类型同时注册了数字 key 和名称 key 时,数字 key 的优先级更高。也就是说,[Oid.bool]会覆盖bool的配置,这一点在排查"为什么我的解码器没生效"时非常重要。
数组类型不用怕:解码器自动套用到每个元素
很多新手会担心int4[]、text[]这类数组类型要单独写解码逻辑。好消息是,deno-postgres 已经帮你处理好了:如果你只为int4定义了解码器,查询int4[]数组时,驱动会自动用parseArray(见query/array_parser.ts)把数组拆开,对每个元素套用同一个解码器。
controls: { decoders: { // 只注册 int4 的解码器 int4: (value: string) => parseInt(value, 10) * 100, }, } // SELECT ARRAY[2, 2, 3] AS scores // 结果:{ scores: [200, 200, 300] }如果你想对数组整体做特殊处理(比如过滤、去重),也可以直接注册int4_array这样的数组类型解码器,它会优先于"基础类型自动套用"逻辑执行:
controls: { decoders: { int4_array: (value: string, _oid, parseArray) => parseArray(value, (v) => parseInt(v, 10) * 10), }, }三种解码方式如何共存?记住这条优先级链
deno-postgres 提供了两种全局策略和一种局部覆盖,理解它们的优先级才能写出不踩坑的代码:
- 自定义解码器(decoders):优先级最高,命中即生效;
- decodeStrategy: "string":把所有字段以原始字符串返回,由你自行解析;
- 默认 auto 策略:按内置规则解析成 JS 原生类型。
controls: { // 全局返回字符串 decodeStrategy: "string", decoders: { // 但布尔字段仍然走自定义解码器 bool: (value: string) => ({ value: value === "t", type: "boolean" }), }, }这条规则在query/decode.ts的decode函数中有清晰体现:先查自定义解码器,再看全局策略,最后才走内置解析。
实战案例:一次配置,格式化 JSONB 与日期
把前面学到的技巧组合起来,看看一个"生产可用"的配置长什么样:
import { Client, Oid } from "./mod.ts"; const client = new Client({ database: "some_db", user: "some_user", controls: { decoders: { // JSONB 字段自动加工 jsonb: (value: string) => { const obj = JSON.parse(value); obj.created_at = new Date(obj.created_at).toISOString(); return obj; }, // 日期字段自动加一天(模拟时区修正) 1082: (value: string) => { const d = new Date(value); return new Date(d.setDate(d.getDate() + 1)); }, }, }, });同样的解码规则在Client、Pool和Transaction中都能使用,一套配置覆盖所有查询路径,维护成本极低。官方测试里还有更多组合玩法,可以参考tests/query_client_test.ts中的 "Custom decoders" 用例。
写在最后
自定义解码器是 deno-postgres 提供的一项"小而美"的能力:配置直观、优先级清晰、对数组类型开箱即用。当你觉得默认类型解析"不够聪明"时,不妨先试试它,而不是在每个查询后面写一堆 map 函数。想深入了解实现细节,可以翻阅项目中的connection/connection_params.ts(类型定义)和query/decode.ts(解码入口),你也可以直接克隆源码仓库,在本地跑一遍测试用例,亲手感受自定义解码器的完整工作流程。
【免费下载链接】postgresPostgreSQL driver for Deno项目地址: https://gitcode.com/gh_mirrors/postgr/postgres
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考