8.4 Go Struct Tags 学习笔记

8.4 Go Struct Tags 学习笔记

1. Struct Tags 是什么

Struct Tags 是附加在结构体字段后面的元数据,用反引号 ` 包裹,为字段提供额外的配置信息:

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email"`
    Password string `json:"-"`
    Age      int    `json:"age,omitempty"`
}

Tags 本身不影响代码逻辑,但可以被 encoding/json、反射等包读取和使用。


2. JSON Tag 的三种用法

基本用法 — 重命名字段

Name string `json:"name"`

序列化时,Go 字段名 Name 变成 JSON 的 "name"(小写):

user := User{ID: 1, Name: "John Doe", Email: "john@example.com"}
json.Marshal(user)
// 输出: {"id":1,"name":"John Doe","email":"john@example.com"}

如果不加 tag,JSON 默认使用 Go 的字段名(大写开头):

{"ID":1,"Name":"John Doe","Email":"john@example.com"}

json:"-" — 排除字段

Password string `json:"-"`

该字段不会出现在 JSON 输出中,适合敏感数据(密码、内部ID等):

user := User{ID: 1, Name: "John Doe", Password: "secret123"}
json.Marshal(user)
// 输出: {"id":1,"name":"John Doe","email":"john@example.com"}
// 注意:Password 和 Age(为0) 都没有出现

json:"name,omitempty" — 零值时省略

Age int `json:"age,omitempty"`

当字段值为零值(int为0,string为"",bool为false)时,JSON 输出中省略该字段:

// Age 为 0(零值)
emptyUser := User{ID: 3, Name: "Empty User", Email: "empty@example.com"}
json.Marshal(emptyUser)
// 输出: {"id":3,"name":"Empty User","email":"empty@example.com"}
// 注意:age 字段被省略了!

// Age 为 30(非零值)
fullUser := User{ID: 4, Name: "Full User", Email: "full@example.com", Age: 30}
json.Marshal(fullUser)
// 输出: {"id":4,"name":"Full User","email":"full@example.com","age":30}
// age 字段出现了

3. JSON 反序列化(Unmarshal)

从 JSON 字符串还原为 Go 结构体,JSON tag 同样发挥作用:

jsonData := `{"id":2,"name":"Jane Smith","email":"jane@example.com","age":25}`

var newUser User
json.Unmarshal([]byte(jsonData), &newUser)
// newUser = {ID:2 Name:"Jane Smith" Email:"jane@example.com" Password:"" Age:25}

JSON 中的 "id" 会根据 tag 映射到 Go 的 ID 字段,"name" 映射到 Name 字段。

为什么 Unmarshal 要传指针 &newUser?

  1. Unmarshal 需要修改 newUser 的值 — 如果传值,Go 会传递结构体的拷贝,函数内部修改的是拷贝,外部的 newUser 不会变。传指针 &newUser 让函数直接修改原变量。

  2. Unmarshal 需要知道目标类型 — 通过指针,Go 可以推断出要反序列化成 User 类型。

对比 Marshal 不需要指针,因为它只读取数据生成 JSON,不修改原值:

json.Marshal(user)              // 读取 user,生成 JSON,不需要指针
json.Unmarshal(data, &newUser)  // 修改 newUser,必须传指针

这是 Go 的通用规则:函数需要修改传入的变量时,必须传指针。


4. 多种 Tag 类型

一个字段可以有多个 tag,用空格分隔:

type Product struct {
    SKU         string  `json:"sku" validate:"required"`
    Name        string  `json:"product_name" validate:"required,min=3"`
    Price       float64 `json:"price" validate:"required,min=0"`
}

type Config struct {
    DBHost     string `json:"db_host" env:"DB_HOST" default:"localhost"`
    DBPort     int    `json:"db_port" env:"DB_PORT" default:"5432"`
}

常见的 tag 类型:

Tag 用途 示例库
json JSON 序列化/反序列化 encoding/json(标准库)
validate 数据校验 github.com/go-playground/validator
env 环境变量映射 github.com/caarlos0/env
default 默认值 自定义或第三方库
db 数据库映射 github.com/jmoiron/sqlx
yaml YAML 序列化 gopkg.in/yaml.v3

5. 反射读取 Struct Tags

使用 reflect 包可以在运行时读取 struct tags:

func printStructTags(v interface{}) {
    t := reflect.TypeOf(v)
    if t.Kind() == reflect.Ptr {
        t = t.Elem()  // 如果传入指针,取其指向的类型
    }

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        // Lookup 返回 tag 值和是否存在的标记
        if jsonTag, ok := field.Tag.Lookup("json"); ok {
            fmt.Printf("    json: %q\n", jsonTag)
        }
        if validateTag, ok := field.Tag.Lookup("validate"); ok {
            fmt.Printf("    validate: %q\n", validateTag)
        }
    }
}

执行结果:

Struct tags for User:
  ID:
    json: "id"
  Name:
    json: "name"
  Email:
    json: "email"
  Password:
    json: "-"
  Age:
    json: "age,omitempty"

Struct tags for Config:
  DBHost:
    json: "db_host"
    env: "DB_HOST"
    default: "localhost"
  DBPort:
    json: "db_port"
    env: "DB_PORT"
    default: "5432"

要点:

  • reflect.TypeOf(v) — 获取类型的反射信息
  • t.NumField() — 获取字段数量
  • field.Tag.Lookup("json") — 查找指定 tag,返回 (值, 是否存在)
  • 传入指针时需要用 t.Elem() 获取实际类型

知识点总结

知识点 关键概念
Struct Tags 反引号包裹的元数据,附加在字段后面
json:"name" JSON 序列化时重命名字段
json:"-" 排除字段,不出现在 JSON 中
json:"name,omitempty" 零值时省略该字段
JSON 反序列化 json.Unmarshal 根据 tag 将 JSON 映射回结构体
多种 Tag 一个字段可同时有 json、validate、env、default 等多种 tag
反射读取 Tags reflect.TypeOf + Tag.Lookup 在运行时读取 tag 信息
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容