You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过反射获取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包解析初始化代码的抽象语法树,提取显式指定的字段名。该方案适合无法修改结构体定义的场景,但实现复杂度较高。

核心思路

  1. 解析包含结构体初始化代码的Go文件
  2. 定位目标结构体的初始化表达式(Lessee{...})
  3. 遍历初始化表达式中的字段赋值节点,提取字段名
  4. 递归处理嵌入结构体的字段赋值

简化版示例代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 23:27:01