
本文介绍如何在 Go 中将形如 ?filter[1][field]=brandId&filter[1][operand]=>&filter[1][values][]=firstvalue 的多维查询字符串,准确解析为可遍历的嵌套数据结构(如 map 或 slice),而非仅依赖扁平化键名匹配。
本文介绍如何在 go 中将形如 `?filter[1][field]=brandid&filter[1][operand]=>&filter[1][values][]=firstvalue` 的多维查询字符串,准确解析为可遍历的嵌套数据结构(如 map 或 slice),而非仅依赖扁平化键名匹配。
Go 标准库的 url.ParseQuery() 会将查询字符串解析为 map[string][]string,但其结果是扁平化键名(如 "filter[1][field]")与字符串切片的映射,无法直接还原嵌套数组或对象结构。要真正构建类似 PHP 的多维数组语义(即 filter[1].field、filter[1].values),需手动解析键名并递归构建结构。
以下是一个健壮、可扩展的解析方案,支持任意层级嵌套(如 filter[0][group][rules][2][op])和重复索引(如 filter[][field]):
package main
import (
"fmt"
"net/url"
"regexp"
"strconv"
"strings"
)
// FilterRule 表示单条过滤规则
type FilterRule struct {
Field string `json:"field,omitempty"`
Operand string `json:"operand,omitempty"`
Values []string `json:"values,omitempty"`
}
// ParseFilters 从查询参数中提取并解析所有 filter[x] 规则
func ParseFilters(query string) ([]FilterRule, error) {
v, err := url.ParseQuery(query)
if err != nil {
return nil, err
}
// 按数字索引分组:filter[0], filter[1], ...
rules := make(map[int]FilterRule)
indexRe := regexp.MustCompile(`filter\[(\d+)\]\[(\w+)\]`)
for key, values := range v {
matches := indexRe.FindStringSubmatchIndex([]byte(key))
if matches == nil {
continue // 跳过非 filter 键
}
// 提取索引和字段名
idxStr := string(key[matches[0][0]+7 : matches[0][1]-1]) // 去掉 "filter[" 和 "]"
field := string(key[matches[1][0] : matches[1][1]]) // 如 "field", "operand", "values"
idx, err := strconv.Atoi(idxStr)
if err != nil {
continue // 忽略非法索引
}
// 初始化该索引对应的规则(若尚未存在)
if _, exists := rules[idx]; !exists {
rules[idx] = FilterRule{}
}
rule := &rules[idx]
switch field {
case "field":
if len(values) > 0 {
rule.Field = values[0]
}
case "operand":
if len(values) > 0 {
rule.Operand = values[0]
}
case "values":
// 支持 values[]=val1&values[]=val2 语法(即多个同名键)
rule.Values = append(rule.Values, values...)
}
}
// 按索引顺序转为 slice(保持原始序号逻辑)
var result []FilterRule
for i := 0; i max {
max = k
}
}
return max
}
func main() {
query := "filter[1][field]=brandId&filter[1][operand]=>&filter[1][values][]=firstvalue&filter[2][field]=price&filter[2][operand]=<p>✅ <strong>关键设计说明</strong>:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件"><img
src="https://img.php.cn/upload/webcode/000/000/164/636a2b4d84031727.png" alt="使用Go语言搭建家庭相册系统-相关课件" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件" class="overflowclass">使用Go语言搭建家庭相册系统-相关课件</a>
<p class="overflowclass">使用Go语言搭建家庭相册系统-相关课件</p>
</div>
<a rel="nofollow" href="/xiazai/learn/7564" title="使用Go语言搭建家庭相册系统-相关课件" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 使用正则 filter\[(\d+)\]\[(\w+)\] 精确提取索引与字段,避免 strings.Contains 的误匹配(如 field 出现在其他键中);
- 显式区分 values[] 多值场景(Go 的 url.ParseQuery 自动将 values[]=a&values[]=b 合并为 []string{"a","b"});
- 支持稀疏索引(如 filter[0] 和 filter[99] 同时存在),并按数字顺序输出;
- 返回结构化 []FilterRule,可直接用于数据库查询构建、API 参数校验等生产场景。
⚠️ 注意事项:
- 若需支持更复杂嵌套(如 filter[0][conditions][1][field]),建议引入通用 JSON-like 解析器(如 gorilla/schema 或自定义递归解析器);
- 生产环境应添加键名白名单校验(防止恶意键如 filter[0][__proto__]);
- URL 编码值(如 brand%20id)会被 url.ParseQuery 自动解码,无需额外处理。
该方案兼顾简洁性与扩展性,使前端传递的多维搜索参数真正落地为 Go 可操作的数据结构。










