field.name为空是因为未导出字段(小写开头)无法被reflect访问,反射只看到零值;必须首字母大写并配tag(如db:"user_name"),嵌套结构体需手动递归处理。

Go 里用 reflect 遍历结构体字段时,为什么 Field.Name 总是空?
因为未导出字段(小写开头)无法被 reflect 访问,反射只看到零值。必须确保结构体字段首字母大写,且有对应 tag(如 db:"user_name"),否则字段直接被跳过。
实操建议:
- 所有需映射的字段必须导出:写成
UserName string,而非userName string - 用
structTag := field.Tag.Get("db")提取 tag,为空时建议 fallback 到field.Name的蛇形转换(如UserName → user_name),但别默认全靠 fallback——显式声明更可靠 - 注意嵌套结构体:反射默认不递归,
field.Type.Kind() == reflect.Struct时要手动进入下一层,否则字段丢失
sqlx 或原生 database/sql 插入时,如何安全拼接字段名和占位符?
不能直接字符串拼接字段名(防 SQL 注入),但占位符本身是安全的;真正危险的是把用户输入当字段名用。字段名必须来自结构体 tag 或白名单校验后的硬编码。
实操建议:
- 字段名列表用
strings.Join(fieldNames, ", ")拼,其中fieldNames来自反射提取的合法 tag 或名称 - 值占位符统一用
?(MySQL/SQLite)或$1, $2(PostgreSQL),从反射顺序取值,保持一一对应 - 避免在反射循环里反复调用
fmt.Sprintf拼 SQL——提前构建好columns和values两个切片再 join,性能更好
结构体字段类型和数据库类型的隐式转换常出什么错?
反射拿到的是 Go 类型(如 int64、time.Time),但 SQL 驱动对底层值的处理很敏感:比如 nil 指针、未初始化的 time.Time{}、float64 精度丢失,都会导致 driver: couldn't parse <code>time as time.Time 这类错误。
实操建议:
- 对
time.Time字段,检查是否为零值:if t.IsZero() { ... },根据业务决定设为 NULL 还是默认时间 - 对指针类型(如
*string),先判空:if field.CanInterface() && field.IsNil() { value = nil },再传给args切片 - 避免用
interface{}直接塞原始值进args——确保最终值是驱动能识别的底层类型(string、int64、[]byte等)
PostgreSQL 的 jsonb 字段或 MySQL 的 JSON 类型怎么反射写入?
反射本身不关心语义,它只管取出字段值;但 JSON 字段要求值是字节流或字符串,而结构体字段可能是普通 struct 或 map。直接传会 panic:“cannot convert struct to string”。
实操建议:
- 检测字段 tag 是否含
json:(如json:"metadata" db:"meta_data"),再判断其 Go 类型是否为 struct/map/slice - 用
json.Marshal序列化后转[]byte写入,不要用fmt.Sprintf("%v")——后者不是标准 JSON 格式 - 注意 PostgreSQL 驱动(如
lib/pq)要求jsonb列绑定值为[]byte,不是string;MySQL 驱动(如go-sql-driver/mysql)则接受string或[]byte
反射做 SQL 生成器最麻烦的从来不是“怎么取字段”,而是“字段值到底该变成什么格式才能让驱动不报错”。每种数据库驱动对空值、时间、JSON 的容忍度都不同,同一套反射逻辑在 SQLite 里跑通,换到 PG 就挂——得按驱动文档逐个对齐行为,而不是指望反射自动兜底。










