DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

psycopg2 写 JSONB 报 can’t adapt type dict:显式选择 JSON 适配

JSONB 列不会替 psycopg2 自动适配 dict。用局部 Json 包装保留参数绑定,区分重复序列化、读写注册、JSON null 与 SQL NULL,并核对全局 hstore 冲突。

PostgreSQL 列已经定义为 JSONB,psycopg2 插入 Python 字典时仍报 can't adapt type 'dict'。数据库知道目标列是 JSONB,不代表驱动会把任何 dict 自动序列化为 JSON;先检查实际传入参数的对象,以及写入路径有没有显式选择 JSON 适配。

以下针对 psycopg2。它与 psycopg 3 的 JSON 包装 API 不同,不要混用导入名称。较旧的 psycopg2 还应核对版本:JSON 适配在 2.5 加入,JSONB 支持在 2.5.4 加入;PostgreSQL 内置 JSONB 从 9.4 开始提供。

在值参数上使用 Json

确认 payload 是要保存成一份 JSON 文档的 Python 对象后,可以在这个值上使用 psycopg2.extras.Json。下面是已有连接与事务中的局部写入示例,mail.payload 是项目已确认的 JSONB 列:

from psycopg2.extras import Json

payload = {"subject": "example", "flags": ["unread"]}
cur.execute(
    "INSERT INTO mail (payload) VALUES (%s)",
    (Json(payload),),
)

参数使用单元素元组,末尾的逗号不能遗漏。%s 是值占位符,不加引号,不用字符串格式化把 JSON 拼进 SQL。表名和列名在示例中是固定结构;若项目需要动态标识符,应另用正确的标识符组合方式,不能拿值占位符替代表名。

Json 默认使用 Python json.dumps 序列化。这解决的是对象如何发送到数据库,并不改变 JSONB 的更新语义:给整列赋一份小字典,仍会替换原值,不会自动与旧文档深度合并。

如果错误还在,检查全部值参数。查询有多个占位符时,可能只是包好了一个字典,其他位置仍传入了未适配的 dict。受控诊断时可以查看参数类型与位置,不必把邮件正文、凭据或业务文档输出到公开日志。

区分 Python 对象与已经序列化的 JSON 文本

payload 若已经是 json.dumps 的返回值,它是字符串。再调用 Json(payload),默认序列化会把这段文本当作一个 JSON 字符串值,而不是原来的对象。写入可能成功,读回的形状却变成字符串。

先约定入口接收 Python 对象还是合法 JSON 文本,避免同一条路径重复序列化。接收外部 JSON 文本时,应先按接口规则解析和校验,再决定采用对象包装;不能仅因它含有花括号就认为一定是有效 JSON 对象。

同样要明确 None 的业务含义。Json(None) 会序列化为 JSON null;普通值参数 None 通常表示 SQL NULL。二者在非空约束、查询和应用读取中有不同意义,不能为了绕过适配错误随意替换。

对象中包含 datetime、Decimal 或自定义类时,默认 json.dumps 也可能无法处理。应定义明确的序列化规则,例如时间格式、精度与类型保留,再提供经过审查的 dumps 函数。把所有未知对象一律 str,会让写入“成功”,也可能破坏原数据含义。

读取注册不能替代写入适配

register_default_jsonb 主要注册从数据库 JSONB 值到 Python 对象的类型转换。调整其中的 loads,解决的是读取解析规则,不会自动让所有写入字典都具备 Json 适配。

psycopg2 也允许用 register_adapter 为 dict 全局注册 Json,但这会改变其他调用方的处理方式,并可能与 register_hstore 注册的字典适配冲突。如果同一进程还把 dict 写入 hstore,不能为了一个 JSONB 字段覆盖原规则。

只有局部字段需要 JSON 序列化时,显式包装对应值更容易检查。确实采用全局注册的项目,应记录注册顺序、所有目标数据类型及影响范围,并验证其他调用入口,不能把它当成无副作用的通用修复。

检查事务结果和读回的形状

适配错误与数据库已经执行 SQL 后的错误,要分开处理。不要假定每次 Python 端序列化失败都已使服务器事务失败;也不要在同一批其他 SQL 失败后,继续使用已进入失败状态的事务。沿项目现有事务管理处理异常,必要时回滚整笔业务操作。

在隔离数据中通过实际应用入口验证正常字典、嵌套数组、空对象、JSON null 与 SQL NULL,并检查读回的值究竟是对象还是字符串。成功标准不只是不再报适配错误,还包括字段内容、形状及事务提交符合接口设计。

修改前保留写入代码与数据处理规则。需要回退时恢复局部适配逻辑,并核对已提交数据;恢复代码不会把误存成字符串的 JSONB 自动还原。数据修正应先区分受影响记录,再按业务校验和备份流程处理。