把 URL query 参数绑定到 Go 结构体的极简工具。
import (
"net/http"
"github.com/cupogo/querybind"
)
func (a *api) listDocuments(w http.ResponseWriter, r *http.Request) {
var spec stores.DocumentSpec
if err := querybind.Bind(&spec, r.URL.Query()); err != nil {
fail(w, r, http.StatusBadRequest, err)
return
}
// ...
}标准库 net/http 拿到的就是 url.Values,querybind.Bind 一行完成解析,
不依赖任何第三方 HTTP 框架,可与 net/http、chi、gin、echo、fasthttp
等任意路由器配合使用。
go get github.com/cupogo/querybind要求 Go 1.25+。
常见的 URL query 绑定库(url-query-binder、go-playground/form v4 等)
只遍历结构体的直接字段。如果你的查询 spec 是把分页、过滤、排序等公共条件
以匿名嵌入的方式组合进来的(例如 comm.PageSpec、pgx.ModelSpec 风格),
那些嵌入字段里的 page / limit / sort 就会被静默忽略。
querybind 围绕这一类场景设计,核心差异:
- 递归展开匿名嵌入——含多层嵌入,含
*struct嵌入(nil 时自动分配); 未导出且为 nil 的指针嵌入会安全跳过,不 panic。 - 枚举统一两种约定——字段先按
encoding.TextUnmarshaler解析; 未实现该接口时,兼容Decode(string) error(许多代码生成器输出的枚举类型 会直接实现这个接口,而不是UnmarshalText)。 - 空值不覆盖非 string 字段——
?limit=不会把已经写好的limit字段抹零, 避免「先给个默认值再被空参清掉」的常见 bug;string字段照常接收空串。 - 切片用重复参数——
?tag=a&tag=b,不做逗号切分;类型由元素决定, 支持[]int、[]MyEnum等任意元素类型。 - 错误带参数名——
querybind: param "page": strconv.ParseInt: ..., 便于直接回给客户端。 - 零依赖——只用标准库(
encoding、reflect、net/url、strconv), 不引入 HTTP 框架或验证器。
| 场景 | 行为 |
|---|---|
| 字段标签 | 默认 form;可通过 New("q") 改为其他标签名 |
form:"-" |
字段跳过,不参与绑定 |
| 缺参 | 忽略;标记 form:"name,required" 时返回错误 |
空值(?x=) |
非 string 字段视为「未提供」,不覆盖零值之外的现值 |
| 切片 | 使用重复参数 ?tag=a&tag=b,按元素类型逐个解析 |
| 枚举 | 优先 encoding.TextUnmarshaler;否则兼容 Decode(string) error |
| 指针字段 | nil 时按需分配;嵌入 *struct 同样按需分配 |
| 匿名嵌入 | 递归展开(多层、未导出嵌入同样支持) |
| 错误格式 | querybind: param "xxx": ... / querybind: missing required param "xxx" |
values 为 nil |
等同于「无任何参数」,required 字段除外 |
| 库 | 匿名嵌入 | Decode(string) error |
空值不覆盖 | 零依赖 |
|---|---|---|---|---|
github.com/techx/form-urlencoded 等手写代码 |
❌ | ❌ | ❌ | ✅ |
go-playground/validator + form |
❌ | ❌ | ❌ | ❌ |
url-query-binder |
❌ | ❌ | ❌ | ✅ |
go-playground/form v4 |
部分 | ❌(需按类型注册) | ❌ | ✅ |
marcsv/go-binder |
❌(只 body) | ❌ | ❌ | ✅ |
querybind |
✅(递归) | ✅ | ✅ | ✅ |
const DefaultTag = "form"
func Bind(obj any, values url.Values) error
func New(tag string) *Binder // tag 为空时使用 DefaultTag
func (b *Binder) Bind(obj any, values url.Values) error
func (b *Binder) Tag() string
func (b *Binder) SetTag(tag string) // 空值忽略Bind 接收的 obj 必须是非 nil 的结构体指针;否则返回
querybind: T is not a struct pointer 形式的错误。
MIT,见 LICENSE。