如何通过反射获取Go结构体实例中已赋值字段的数组?
获取Go结构体实例中显式赋值的字段名
结构体定义
type Lessee struct { Name string `json:"name" gorm:"name"` ShortName string `json:"shortName" gorm:"short_name"` Code string `json:"code" gorm:"code"` CreditCode string `json:"creditCode" gorm:"credit_code"` UnitMan string `json:"unitMan" gorm:"unit_man"` UnitMobile string `json:"unitMobile" gorm:"unit_mobile"` UnitEmail string `json:"unitEmail" gorm:"unit_email"` UnitPhone string `json:"unitPhone" gorm:"unit_phone"` Remark string `json:"remark" gorm:"remark"` Tenancy int `json:"tenancy" gorm:"tenancy"` BeginTime time.Time `json:"beginTime" gorm:"begin_time"` EndTime time.Time `json:"endTime" gorm:"end_time"` AppLimit int `json:"appLimit" gorm:"app_limit"` ManagerId string `json:"managerId" gorm:"manager_id"` CipherId string `json:"cipherId" gorm:"cipher_id"` State int `json:"state" gorm:"state"` BaseModel } type BaseModel struct { MinBaseModel UpdateBy string `json:"updateBy" gorm:"column:update_by"` UpdateTime time.Time `json:"updateTime" gorm:"column:update_time"` } type MinBaseModel struct { Id string `json:"id" gorm:"column:id;primary_key"` CreateBy string `json:"createBy" gorm:"column:create_by"` CreateTime time.Time `json:"createTime" gorm:"column:create_time"` }
实例示例
lessee := Lessee{ Name: "Jayleonc Company", ShortName: "Jay", CreditCode: "91440101MA9Y9T9K3A", UnitMan: "JJ", UnitMobile: "13333333333", UnitPhone: "020-1111111", UnitEmail: "jj@gmail.com", Remark: "", Tenancy: 14, AppLimit: 10, BeginTime: time.Now(), BaseModel: BaseModel{ UpdateTime: time.Now(), UpdateBy: "Jayleonc", MinBaseModel: MinBaseModel{ Id: "08fe01037faeb74ce2e82f92651c2109", }, }, }
需求说明
需要提取该实例中显式赋值的字段名数组,要求:
- 包含所有初始化时显式指定的字段,即使字段值为零值(如
Remark: "") - 排除未显式赋值的字段(如
Code、CreateBy等)
期望输出:
[]string{"Name", "ShortName", "CreditCode", "UnitMan", "UnitMobile", "UnitEmail", "UnitPhone", "Remark", "Tenancy", "BeginTime", "AppLimit", "UpdateBy", "UpdateTime", "Id"}
问题分析
Go的值类型字段在运行时无法区分「显式赋值的零值」和「默认零值」,因为二者的内存状态完全一致。直接使用reflect或encoding/json无法满足需求,因为它们会把所有零值字段(包括未赋值的)统一处理。
解决方案
方案1:修改结构体字段为指针类型(推荐)
将结构体的所有值类型字段改为指针类型,未赋值的字段为nil,显式赋值的字段(哪怕是零值)为非nil指针,以此区分是否显式赋值。
修改后的结构体定义
type Lessee struct { Name *string `json:"name" gorm:"name"` ShortName *string `json:"shortName" gorm:"short_name"` Code *string `json:"code" gorm:"code"` CreditCode *string `json:"creditCode" gorm:"credit_code"` UnitMan *string `json:"unitMan" gorm:"unit_man"` UnitMobile *string `json:"unitMobile" gorm:"unit_mobile"` UnitEmail *string `json:"unitEmail" gorm:"unit_email"` UnitPhone *string `json:"unitPhone" gorm:"unit_phone"` Remark *string `json:"remark" gorm:"remark"` Tenancy *int `json:"tenancy" gorm:"tenancy"` BeginTime *time.Time `json:"beginTime" gorm:"begin_time"` EndTime *time.Time `json:"endTime" gorm:"end_time"` AppLimit *int `json:"appLimit" gorm:"app_limit"` ManagerId *string `json:"managerId" gorm:"manager_id"` CipherId *string `json:"cipherId" gorm:"cipher_id"` State *int `json:"state" gorm:"state"` BaseModel } type BaseModel struct { MinBaseModel UpdateBy *string `json:"updateBy" gorm:"column:update_by"` UpdateTime *time.Time `json:"updateTime" gorm:"column:update_time"` } type MinBaseModel struct { Id *string `json:"id" gorm:"column:id;primary_key"` CreateBy *string `json:"createBy" gorm:"column:create_by"` CreateTime *time.Time `json:"createTime" gorm:"column:create_time"` }
初始化实例
name := "Jayleonc Company" shortName := "Jay" creditCode := "91440101MA9Y9T9K3A" unitMan := "JJ" unitMobile := "13333333333" unitPhone := "020-1111111" unitEmail := "jj@gmail.com" remark := "" tenancy := 14 appLimit := 10 beginTime := time.Now() updateTime := time.Now() updateBy := "Jayleonc" id := "08fe01037faeb74ce2e82f92651c2109" lessee := Lessee{ Name: &name, ShortName: &shortName, CreditCode: &creditCode, UnitMan: &unitMan, UnitMobile: &unitMobile, UnitPhone: &unitPhone, UnitEmail: &unitEmail, Remark: &remark, Tenancy: &tenancy, AppLimit: &appLimit, BeginTime: &beginTime, BaseModel: BaseModel{ UpdateTime: &updateTime, UpdateBy: &updateBy, MinBaseModel: MinBaseModel{ Id: &id, }, }, }
提取显式赋值字段的函数
import "reflect" func GetExplicitFields(v interface{}) []string { var fields []string val := reflect.ValueOf(v).Elem() // 传入指针,取Elem()获取结构体值 typ := val.Type() for i := 0; i < val.NumField(); i++ { field := val.Field(i) fieldType := typ.Field(i) // 递归处理嵌入结构体 if fieldType.Anonymous { embeddedFields := GetExplicitFields(field.Addr().Interface()) fields = append(fields, embeddedFields...) continue } // 非nil指针即为显式赋值的字段 if !field.IsNil() { fields = append(fields, fieldType.Name) } } return fields }
使用示例
// 传入实例指针 explicitFields := GetExplicitFields(&lessee) fmt.Println(explicitFields) // 输出符合预期的字段数组
方案2:编译期静态分析(无需修改结构体)
使用Go的go/ast包解析初始化代码的抽象语法树,提取显式指定的字段名。该方案适合无法修改结构体定义的场景,但实现复杂度较高。
核心思路
- 解析包含结构体初始化代码的Go文件
- 定位目标结构体的初始化表达式(
Lessee{...}) - 遍历初始化表达式中的字段赋值节点,提取字段名
- 递归处理嵌入结构体的字段赋值
简化版示例代码
import ( "go/ast" "go/parser" "go/token" ) func ParseExplicitFields(filePath string, structName string) ([]string, error) { fset := token.NewFileSet() node, err := parser.ParseFile(fset, filePath, nil, parser.AllErrors) if err != nil { return nil, err } var fields []string // 遍历AST节点查找结构体初始化表达式 ast.Inspect(node, func(n ast.Node) bool { if compLit, ok := n.(*ast.CompositeLit); ok { if ident, ok := compLit.Type.(*ast.Ident); ok && ident.Name == structName { // 遍历所有字段赋值 for _, elt := range compLit.Elts { if kv, ok := elt.(*ast.KeyValueExpr); ok { key := kv.Key.(*ast.Ident).Name // 处理嵌入结构体的初始化 if innerCompLit, ok := kv.Value.(*ast.CompositeLit); ok { if innerIdent, ok := innerCompLit.Type.(*ast.Ident); ok { innerFields, _ := ParseExplicitFields(filePath, innerIdent.Name) fields = append(fields, innerFields...) } } else { fields = append(fields, key) } } } } } return true }) // 去重处理重复赋值的字段 uniqueFields := make([]string, 0, len(fields)) seen := make(map[string]bool) for _, f := range fields { if !seen[f] { seen[f] = true uniqueFields = append(uniqueFields, f) } } return uniqueFields, nil }
使用示例
fields, err := ParseExplicitFields("your_file.go", "Lessee") if err != nil { log.Fatal(err) } fmt.Println(fields) // 输出显式赋值的字段数组
注意事项
- 方案1的指针类型字段需要注意空指针问题,使用前需判断是否为
nil - 方案2仅能解析编译期的初始化代码,无法处理运行时动态赋值的字段
内容的提问来源于stack exchange,提问作者Jayleonc
相关产品推荐
相关产品推荐

