You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Go+sqlx+pgtype.JSONB写入PostgreSQL JSONB字段时出现十六进制编码导致JSON语法错误的问题咨询

使用Go+sqlx+pgtype.JSONB写入PostgreSQL JSONB字段时出现十六进制编码导致JSON语法错误的问题咨询

看起来你在使用Go结合sqlx和pgtype.JSONB写入PostgreSQL的JSONB字段时踩了个编码的坑,我来帮你一步步拆解问题、分析原因,最后给出正确的解决方案。

为什么pgtype.JSONB会被转成十六进制发送?

这本质上不是pgtype或sqlx的驱动问题,而是参数传递方式错误导致的:
当你没有使用参数化查询,而是手动将pgtype.JSONB的Bytes字段内容拼接到SQL语句中时,驱动会把字节数组当成二进制数据处理,自动编码成PostgreSQL的十六进制字符串格式(比如\x7b2264656661756c7422...)。但PostgreSQL的JSONB字段期望接收的是JSON格式的文本,不是二进制的十六进制表示,所以才会抛出"invalid input syntax for type json"的错误。

你之前尝试的dest.Set(data)和直接赋值dest.Bytes = data; dest.Status = pgtype.Present都是正确的赋值方式——问题出在后续的SQL参数传递环节,而不是赋值本身。

是不是sqlx/pgtype的驱动问题?

不是。github.com/jackc/pgtypeJSONB类型已经实现了Go标准库database/sqlValuerScanner接口:

  • Valuer接口会自动将pgtype.JSONB实例序列化为PostgreSQL能识别的JSONB格式
  • Scanner接口会自动将PostgreSQL返回的JSONB数据解析为pgtype.JSONB实例

只要你正确使用参数化查询,sqlx会自动调用这些接口方法,不会出现十六进制编码的问题。

要不要改用*string字段代替?

可以作为临时的 workaround,但并不推荐:

  • *string字段需要你自己手动处理JSON序列化/反序列化、NULL值判断、JSON格式验证,冗余代码更多
  • pgtype.JSONB提供了类型安全的JSONB处理:比如调用Set()方法时会自动验证JSON的有效性,通过Status字段原生支持Present/Null/Undefined三种状态,更贴合PostgreSQL JSONB字段的特性

正确的存储方式是什么?

核心原则是永远使用参数化查询,避免手动拼接SQL参数。以下是完整的示例代码:

1. 定义结构体与赋值

import (
    "encoding/json"
    "github.com/jackc/pgtype"
    "github.com/jmoiron/sqlx"
)

type Platform struct {
    Logo       pgtype.JSONB `db:"logo"`
    Motivation pgtype.JSONB `db:"motivation"`
    Rules      pgtype.JSONB `db:"rules"`
}

// 构造并赋值pgtype.JSONB字段
func newPlatform() (Platform, error) {
    var p Platform

    // 处理Logo字段(使用Set方法,自动验证JSON有效性)
    logoData := map[string]string{"default": "https://example.com/logo.png"}
    logoBytes, err := json.Marshal(logoData)
    if err != nil {
        return Platform{}, err
    }
    if err := p.Logo.Set(logoBytes); err != nil {
        return Platform{}, err
    }

    // 处理Motivation字段(直接赋值方式)
    motivationData := map[string]string{"text": "Build amazing things"}
    motivationBytes, err := json.Marshal(motivationData)
    if err != nil {
        return Platform{}, err
    }
    p.Motivation.Bytes = motivationBytes
    p.Motivation.Status = pgtype.Present

    // 处理Rules字段(如果要存NULL)
    p.Rules.Status = pgtype.Null

    return p, nil
}

2. 使用参数化查询插入

推荐用sqlx.NamedExec(命名参数更清晰,和结构体标签对应):

func insertPlatform(db *sqlx.DB, p Platform) error {
    _, err := db.NamedExec(`
        INSERT INTO platforms (logo, motivation, rules)
        VALUES (:logo, :motivation, :rules)
    `, p)
    return err
}

也可以用普通的Exec配合位置占位符:

func insertPlatform(db *sqlx.DB, p Platform) error {
    _, err := db.Exec(`
        INSERT INTO platforms (logo, motivation, rules)
        VALUES ($1, $2, $3)
    `, p.Logo, p.Motivation, p.Rules)
    return err
}

3. 额外注意事项

  • 确保使用最新版的github.com/jackc/pgtype,旧版本可能存在兼容性问题
  • 读取数据时,直接用sqlx的Select/QueryRow扫描到Platform结构体即可,pgtype.JSONB会自动解析数据:
    var p Platform
    err := db.Get(&p, "SELECT logo, motivation, rules FROM platforms WHERE id = $1", 1)
    

内容来源于stack exchange

火山引擎 最新活动