结构体标签必须用反引号包裹,值需双引号包围且键值间用空格分隔;错误写法如双引号包裹标签、逗号分隔或缺双引号会导致reflect.tag.get()返回空字符串,引发json序列化异常、校验跳过等静默问题。

结构体标签不是注释,也不能用双引号包裹
写错标签语法是新手最常踩的坑,编译不报错但 reflect.Tag.Get() 返回空字符串,导致 JSON 序列化字段名不对、校验逻辑跳过、ORM 映射失败——所有问题都静默发生。
必须用反引号 ` 包裹整个标签字符串;键值对之间用空格分隔,不能用逗号;值必须用双引号 " 包裹,哪怕只是 "-" 或 "omitempty"。
- ✅ 正确:
Name string `json:"name" validate:"required"` - ❌ 错误:
Name string "json:\"name\""(双引号 → 解析为普通字符串) - ❌ 错误:
Name string `json:"name",validate:"required"`(逗号 → 反射无法拆分键值对) - ❌ 错误:
Name string `json:name`(缺双引号 → 语法非法,go vet 会警告)
json 标签里常见的组合陷阱
json 标签看似简单,但多个修饰符叠加时顺序和语义容易混淆,尤其 omitempty 和 string 同时出现时行为有隐含依赖。
-
Age int `json:"age,omitempty"`:当Age == 0时字段被省略(注意:0 是整型零值) -
Price float64 `json:"price,string,omitempty"`:先转成字符串再判断是否省略;若Price == 0.0,最终输出"0"而非省略(因为字符串"0"非空) -
ID uint `json:"id,omitempty,string"`:顺序不影响解析,但语义上建议把string放后面,避免误读为“先省略再转字符串” -
CreatedAt time.Time `json:"created_at,omitempty"`:time.Time{}是零值,会被省略;但如果你希望零时间也输出,就得去掉omitempty或用指针*time.Time
自定义标签如 validate 必须手动解析
标准库不认 validate、doc、swagger 这类标签,它们纯粹是业务约定。想让它生效,必须自己调用 reflect.StructField.Tag.Get("validate"),然后写逻辑解析字符串内容。
比如 validate:"required,min=5,max=20" 这个值,Tag.Get("validate") 返回的就是原始字符串,不会自动拆成 map 或 struct —— 解析全靠你自己或第三方库(如 go-playground/validator)。
- 别指望
reflect帮你做规则校验,它只负责“读出来” - 空字符串
""和显式忽略"-"含义不同:前者可能表示未设置,后者是明确跳过 - 多个自定义标签共存时(如
json:"user_id" db:"uid" validate:"required"),互不干扰,各自被对应模块读取
反射读取标签时 field.Tag 是只读字符串
field.Tag 是 reflect.StructTag 类型,本质是封装了字符串的只读结构。你不能修改它,也不能直接用 strings.Split 解析 —— 必须用 .Get(key) 方法提取键对应的值。
这个类型内部做了空格和引号的简单解析,但仅限于标准格式。如果你手写了一个非法格式(比如漏掉引号或用错分隔符),.Get() 会返回空,而不是 panic 或报错。
- 错误写法:
strings.Split(string(field.Tag), " ")→ 破坏引号内空格,解析错误 - 正确写法:
field.Tag.Get("json")、field.Tag.Get("db") - 检查是否存在:
if tag := field.Tag.Get("validate"); tag != "" && tag != "-" { /* 处理 */ }
真正难的不是写标签,而是确保每个用到它的库(json、gorm、validator)都按同一套规则理解那个字符串 —— 这种隐式契约,比代码更需要文档和团队共识。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











